ESP32 IDF GET_HTTPS
学习了之前的网络控件,分区表还有OTA升级之后我们将进入esp32 idf的get_https的学习。
关于https和http的介绍可以在往期的博客中找到
1.先上代码
官方的https示例代码一共可以差分成
1.1 定义部分:
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "protocol_examples_common.h"
#include "esp_sntp.h"
#include "esp_netif.h"
#include "component/cJSON.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"#include "esp_tls.h"
#include "sdkconfig.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
#include "esp_crt_bundle.h"
#endif
#include "time_sync.h"/* Constants that aren't configurable in menuconfig */
#ifdef CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT
#define WEB_SERVER "tls13.browserleaks.com"
#define WEB_PORT "443"
#define WEB_URL "https://tls13.browserleaks.com/tls"
#else
#define WEB_SERVER "api.bilibili.com"
#define WEB_PORT "443"
#define WEB_URL "https://api.bilibili.com/x/relation/stat?vmid=503330149"
#endif#define SERVER_URL_MAX_SZ 256static const char *TAG = "example";/* Timer interval once every day (24 Hours) */
#define TIME_PERIOD (86400000000ULL)static const char HOWSMYSSL_REQUEST[] = "GET " WEB_URL " HTTP/1.1\r\n""Host: "WEB_SERVER"\r\n""\r\n";// Wi-Fi配置参数(替换为你的实际信息)
#define WIFI_SSID "Gary_Studio"
#define WIFI_PASS "123456789"
#define WIFI_CONNECT_TIMEOUT 15000 // 15秒超时// Wi-Fi事件组
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS
static const char LOCAL_SRV_REQUEST[] = "GET " CONFIG_EXAMPLE_LOCAL_SERVER_URL " HTTP/1.1\r\n""Host: "WEB_SERVER"\r\n""\r\n";
#endif
1.2 TLS/SSL 安全通信配置的核心部分
extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end");extern const uint8_t local_server_cert_pem_start[] asm("_binary_local_server_cert_pem_start");
extern const uint8_t local_server_cert_pem_end[] asm("_binary_local_server_cert_pem_end");
#if CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
#if defined(CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT)
static const int server_supported_ciphersuites[] = {MBEDTLS_TLS1_3_AES_256_GCM_SHA384, MBEDTLS_TLS1_3_AES_128_CCM_SHA256, 0};
static const int server_unsupported_ciphersuites[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256, 0};
#else
static const int server_supported_ciphersuites[] = {MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384, MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 0};
static const int server_unsupported_ciphersuites[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256, 0};
#endif // CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT
#endif // CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS
static esp_tls_client_session_t *tls_client_session = NULL;
static bool save_client_session = false;
#endif
1.3 之前博客的网络st部分
static void wifi_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{static int s_retry_num = 0;if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {if (s_retry_num < 3) {esp_wifi_connect();s_retry_num++;ESP_LOGI(TAG, "Wi-Fi断开,重试连接(第%d次)", s_retry_num);} else {xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);}ESP_LOGI(TAG, "等待Wi-Fi连接...");} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;ESP_LOGI(TAG, "获取IP地址: " IPSTR, IP2STR(&event->ip_info.ip));s_retry_num = 0;xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);}
}static esp_err_t wifi_init_sta(void)
{s_wifi_event_group = xEventGroupCreate();// 初始化网络接口esp_netif_init();esp_event_loop_create_default();esp_netif_create_default_wifi_sta();// 初始化Wi-Fi驱动wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));// 注册事件处理器esp_event_handler_instance_t instance_any_id;esp_event_handler_instance_t instance_got_ip;ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,&instance_any_id));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&wifi_event_handler,NULL,&instance_got_ip));// 配置Wi-Fi参数wifi_config_t wifi_config = {.sta = {.ssid = WIFI_SSID,.password = WIFI_PASS,.threshold.authmode = WIFI_AUTH_WPA2_PSK,},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());ESP_LOGI(TAG, "正在连接Wi-Fi: %s", WIFI_SSID);// 等待连接结果EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,pdFALSE,pdFALSE,WIFI_CONNECT_TIMEOUT / portTICK_PERIOD_MS);// 处理连接结果if (bits & WIFI_CONNECTED_BIT) {ESP_LOGI(TAG, "Wi-Fi连接成功");return ESP_OK;} else if (bits & WIFI_FAIL_BIT) {ESP_LOGE(TAG, "Wi-Fi连接失败(重试3次后)");return ESP_FAIL;} else {ESP_LOGE(TAG, "Wi-Fi连接超时(%dms)", WIFI_CONNECT_TIMEOUT);return ESP_FAIL;}
}
1.4 https接口核心部分
static void https_get_request(esp_tls_cfg_t cfg, const char *WEB_SERVER_URL, const char *REQUEST)
{char buf[512];int ret, len;esp_tls_t *tls = esp_tls_init();if (!tls) {ESP_LOGE(TAG, "Failed to allocate esp_tls handle!");goto exit;}if (esp_tls_conn_http_new_sync(WEB_SERVER_URL, &cfg, tls) == 1) {ESP_LOGI(TAG, "Connection established...");} else {ESP_LOGE(TAG, "Connection failed...");int esp_tls_code = 0, esp_tls_flags = 0;esp_tls_error_handle_t tls_e = NULL;esp_tls_get_error_handle(tls, &tls_e);/* Try to get TLS stack level error and certificate failure flags, if any */ret = esp_tls_get_and_clear_last_error(tls_e, &esp_tls_code, &esp_tls_flags);if (ret == ESP_OK) {ESP_LOGE(TAG, "TLS error = -0x%x, TLS flags = -0x%x", esp_tls_code, esp_tls_flags);}goto cleanup;}size_t written_bytes = 0;do {ret = esp_tls_conn_write(tls,REQUEST + written_bytes,strlen(REQUEST) - written_bytes);if (ret >= 0) {ESP_LOGI(TAG, "%d bytes written", ret);written_bytes += ret;} else if (ret != ESP_TLS_ERR_SSL_WANT_READ && ret != ESP_TLS_ERR_SSL_WANT_WRITE) {ESP_LOGE(TAG, "esp_tls_conn_write returned: [0x%02X](%s)", ret, esp_err_to_name(ret));goto cleanup;}} while (written_bytes < strlen(REQUEST));ESP_LOGI(TAG, "Reading HTTP response...");do {len = sizeof(buf) - 1;memset(buf, 0x00, sizeof(buf));ret = esp_tls_conn_read(tls, (char *)buf, len);if (ret == ESP_TLS_ERR_SSL_WANT_WRITE || ret == ESP_TLS_ERR_SSL_WANT_READ) {continue;} else if (ret < 0) {ESP_LOGE(TAG, "esp_tls_conn_read returned [-0x%02X](%s)", -ret, esp_err_to_name(ret));break;} else if (ret == 0) {ESP_LOGI(TAG, "connection closed");break;}len = ret;ESP_LOGD(TAG, "%d bytes read", len);/* Print response directly to stdout as it is read */for (int i = 0; i < len; i++) {putchar(buf[i]);}putchar('\n'); // JSON output doesn't have a newline at end} while (1);#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS/* The TLS session is successfully established, now saving the session ctx for reuse */if (save_client_session) {esp_tls_free_client_session(tls_client_session);tls_client_session = esp_tls_get_client_session(tls);}
#endifcleanup:esp_tls_conn_destroy(tls);
exit:for (int countdown = 10; countdown >= 0; countdown--) {ESP_LOGI(TAG, "%d...", countdown);vTaskDelay(1000 / portTICK_PERIOD_MS);}
}
1.5 给核心函数传参的部分
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
static void https_get_request_using_crt_bundle(void)
{ESP_LOGI(TAG, "https_request using crt bundle");esp_tls_cfg_t cfg = {.crt_bundle_attach = esp_crt_bundle_attach,};https_get_request(cfg, WEB_URL, HOWSMYSSL_REQUEST);
}
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLSstatic void https_get_request_using_cacert_buf(void)
{ESP_LOGI(TAG, "https_request using cacert_buf");esp_tls_cfg_t cfg = {.cacert_buf = (const unsigned char *) server_root_cert_pem_start,.cacert_bytes = server_root_cert_pem_end - server_root_cert_pem_start,};https_get_request(cfg, WEB_URL, HOWSMYSSL_REQUEST);
}static void https_get_request_using_specified_ciphersuites(void)
{
#if CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLSESP_LOGI(TAG, "https_request using server supported ciphersuites");esp_tls_cfg_t cfg = {.cacert_buf = (const unsigned char *) server_root_cert_pem_start,.cacert_bytes = server_root_cert_pem_end - server_root_cert_pem_start,.ciphersuites_list = server_supported_ciphersuites,};https_get_request(cfg, WEB_URL, HOWSMYSSL_REQUEST);ESP_LOGI(TAG, "https_request using server unsupported ciphersuites");cfg.ciphersuites_list = server_unsupported_ciphersuites;https_get_request(cfg, WEB_URL, HOWSMYSSL_REQUEST);
#endif
}
1.6 https任务
static void https_request_task(void *pvparameters)
{ESP_LOGI(TAG, "Start https_request example");#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETSchar *server_url = NULL;
#ifdef CONFIG_EXAMPLE_LOCAL_SERVER_URL_FROM_STDINchar url_buf[SERVER_URL_MAX_SZ];if (strcmp(CONFIG_EXAMPLE_LOCAL_SERVER_URL, "FROM_STDIN") == 0) {example_configure_stdin_stdout();fgets(url_buf, SERVER_URL_MAX_SZ, stdin);int len = strlen(url_buf);url_buf[len - 1] = '\0';server_url = url_buf;} else {ESP_LOGE(TAG, "Configuration mismatch: invalid url for local server");abort();}printf("\nServer URL obtained is %s\n", url_buf);
#elseserver_url = CONFIG_EXAMPLE_LOCAL_SERVER_URL;
#endif /* CONFIG_EXAMPLE_LOCAL_SERVER_URL_FROM_STDIN */https_get_request_to_local_server(server_url);https_get_request_using_already_saved_session(server_url);
#endif#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLShttps_get_request_using_crt_bundle();
#endifESP_LOGI(TAG, "Minimum free heap size: %" PRIu32 " bytes", esp_get_minimum_free_heap_size());https_get_request_using_cacert_buf();https_get_request_using_global_ca_store();https_get_request_using_specified_ciphersuites();ESP_LOGI(TAG, "Finish https_request example");vTaskDelete(NULL);
}
1.7 主函数
void app_main(void)
{ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.* Read "Establishing Wi-Fi or Ethernet Connection" section in* examples/protocols/README.md for more information about this function.*/ESP_ERROR_CHECK(wifi_init_sta());if (esp_reset_reason() == ESP_RST_POWERON) {ESP_LOGI(TAG, "Updating time from NVS");ESP_ERROR_CHECK(update_time_from_nvs());}const esp_timer_create_args_t nvs_update_timer_args = {.callback = (void *)&fetch_and_store_time_in_nvs,};esp_timer_handle_t nvs_update_timer;ESP_ERROR_CHECK(esp_timer_create(&nvs_update_timer_args, &nvs_update_timer));ESP_ERROR_CHECK(esp_timer_start_periodic(nvs_update_timer, TIME_PERIOD));xTaskCreate(&https_request_task, "https_get_task", 8192, NULL, 5, NULL);
}
2. 代码流程讲解
2.1 整体代码执行流程(含非 HTTPS 逻辑)
- 初始化与任务创建:主函数中执行一系列初始化操作(如网络初始化、ESP-TLS 环境初始化等),并创建 HTTPS 任务。
- 任务分支选择:在 HTTPS 任务中,根据需求选择进入以下三个函数之一:
https_get_request_using_global_ca_store(void)
:适用于需访问多个服务器(依赖全局 CA 证书验证)的场景;https_get_request_using_specified_ciphersuites(void)
:适用于需指定加密套件的场景;https_get_request_using_cacert_buf(void)
:适用于需访问不同服务器(使用局部 CA 证书验证)的场景。
- 全局 CA 存储流程:在
https_get_request_using_global_ca_store
函数中,先通过esp_tls_set_global_ca_store
设置全局 CA 证书存储,再在esp_tls_cfg_t
结构体中启用use_global_ca_store
标志位,最后将配置传入https_get_request
函数并调用。 - TLS 句柄初始化:在
https_get_request
函数中,通过esp_tls_init()
初始化 TLS 句柄,用于后续 TLS 连接管理。 - 建立连接:调用
esp_tls_conn_http_new_sync(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls)
函数,阻塞式连接至 HTTP/HTTPS 服务器。 - 发送数据:使用
esp_tls_conn_write(esp_tls_t *tls, const void *data, size_t datalen)
向服务器发送数据。若数据未完全发送(返回值小于datalen
),需根据返回值循环发送剩余部分。 - 接收数据:通过
esp_tls_conn_read(tls, (char *)buf, len)
读取服务器响应,返回值需区分以下情况:- 正数:成功接收的字节数;
- 0:连接正常关闭;
- 负数:发生错误(如
ESP_TLS_ERR_SSL_WANT_READ
表示需等待数据,非实质错误)。
2.2 核心 HTTPS 代码执行步骤
- 配置 TLS 参数:定义并初始化
esp_tls_cfg_t
结构体(如 CA 证书、加密套件、协议版本等); - 初始化 TLS 句柄:调用
esp_tls_init()
创建并初始化 TLS 句柄; - 阻塞建立连接:通过
esp_tls_conn_http_new_sync
与服务器完成 TLS 握手,建立加密连接; - 发送请求数据:使用
esp_tls_conn_write
向服务器发送 HTTP 请求(如 GET/POST 数据); - 接收响应数据:通过
esp_tls_conn_read
读取服务器返回的 HTTPS 响应数据。
3.拓展
3.1 关键宏定义开关及启用方式
代码中涉及的核心宏定义开关及作用如下:
CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
- 作用:启用 mbedTLS 作为 ESP-TLS 的底层加密库(mbedTLS 是轻量级嵌入式加密库,支持 TLS 协议及各类加密算法)。
- 启用方式:通过
idf.py menuconfig
进入配置界面,在Example Configuration
中勾选 “Use mbedTLS with ESP-TLS”。
CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT
- 作用:控制客户端是否使用 TLS 1.3 协议。TLS 1.3 相比 TLS 1.2 安全性更高(强制前向保密、移除弱加密套件)、握手更快(1 个 RTT)。
- 启用方式:在
menuconfig
的Example Configuration
中,找到 “SSL/TLS protocol version for client”,选择 “TLS 1.3”。
CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS
- 作用:启用客户端 TLS 会话票证(Session Ticket)功能,实现会话复用。首次握手后,客户端存储服务器下发的加密票证,下次连接时直接提交票证,跳过密钥协商,减少延迟和资源消耗。
- 启用方式:在
menuconfig
的Example Configuration
中勾选 “Enable client session tickets”。
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
- 作用:启用 mbedTLS 根证书捆绑功能,将 Mozilla 信任的根 CA 证书打包嵌入固件,客户端可自动从捆绑包中获取根证书验证服务器,无需手动指定单个 CA 证书。
- 启用方式:在
menuconfig
中进入Component config → mbedTLS → Certificate Bundle
,勾选 “Enable MBEDTLS_CERTIFICATE_BUNDLE”,并选择证书范围(部分 / 完整)。
3.2 拓展性
1.https的连接api不一定用代码上的,还可以使用esp_tls_conn_http_new(阻塞) 和 esp_tls_conn_http_new_async(非阻塞)等
4.输出结果及调试记录
注意:在调试的时候遇到过时间同步的问题,这个需要注意