ESP32 IDF GET_HTTPS

学习了之前的网络控件,分区表还有OTA升级之后我们将进入esp32 idf的get_https的学习。

关于https和http的介绍可以在往期的博客中找到

1.先上代码

官方的https示例代码一共可以差分成

1.1 定义部分:

cpp 复制代码
#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 256

static 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 安全通信配置的核心部分

cpp 复制代码
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部分

cpp 复制代码
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接口核心部分

cpp 复制代码
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);
    }
#endif

cleanup:
    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 给核心函数传参的部分

cpp 复制代码
#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_MBEDTLS

static 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_MBEDTLS

    ESP_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任务

cpp 复制代码
static void https_request_task(void *pvparameters)
{
    ESP_LOGI(TAG, "Start https_request example");

#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS
    char *server_url = NULL;
#ifdef CONFIG_EXAMPLE_LOCAL_SERVER_URL_FROM_STDIN
    char 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);
#else
    server_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_MBEDTLS
    https_get_request_using_crt_bundle();
#endif
    ESP_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 主函数

cpp 复制代码
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());

        // 初始化并等待SNTP时间同步(使用新的API消除警告)
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);  // 替换旧函数sntp_setoperatingmode
    esp_sntp_setservername(0, "pool.ntp.org");    // 替换旧函数sntp_setservername
    esp_sntp_init();  // 注意:原代码中是sntp_init(),同样替换为esp_sntp_init()

    struct tm timeinfo = {0};
    time_t now = 0;
    int retry = 0;
    const int retry_count = 20;
    ESP_LOGI(TAG, "等待时间同步...");
    while (timeinfo.tm_year < (2020 - 1900) && ++retry < retry_count) {
        ESP_LOGI(TAG, "等待SNTP同步 (%d/%d)...", retry, retry_count);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        time(&now);
        localtime_r(&now, &timeinfo);
    }

    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 逻辑)

  1. 初始化与任务创建:主函数中执行一系列初始化操作(如网络初始化、ESP-TLS 环境初始化等),并创建 HTTPS 任务。
  2. 任务分支选择 :在 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 证书验证)的场景。
  3. 全局 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函数并调用。
  4. TLS 句柄初始化 :在https_get_request函数中,通过esp_tls_init()初始化 TLS 句柄,用于后续 TLS 连接管理。
  5. 建立连接 :调用esp_tls_conn_http_new_sync(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls)函数,阻塞式连接至 HTTP/HTTPS 服务器。
  6. 发送数据 :使用esp_tls_conn_write(esp_tls_t *tls, const void *data, size_t datalen)向服务器发送数据。若数据未完全发送(返回值小于datalen),需根据返回值循环发送剩余部分。
  7. 接收数据 :通过esp_tls_conn_read(tls, (char *)buf, len)读取服务器响应,返回值需区分以下情况:
    • 正数:成功接收的字节数;
    • 0:连接正常关闭;
    • 负数:发生错误(如ESP_TLS_ERR_SSL_WANT_READ表示需等待数据,非实质错误)。

2.2 核心 HTTPS 代码执行步骤

  1. 配置 TLS 参数 :定义并初始化esp_tls_cfg_t结构体(如 CA 证书、加密套件、协议版本等);
  2. 初始化 TLS 句柄 :调用esp_tls_init()创建并初始化 TLS 句柄;
  3. 阻塞建立连接 :通过esp_tls_conn_http_new_sync与服务器完成 TLS 握手,建立加密连接;
  4. 发送请求数据 :使用esp_tls_conn_write向服务器发送 HTTP 请求(如 GET/POST 数据);
  5. 接收响应数据 :通过esp_tls_conn_read读取服务器返回的 HTTPS 响应数据。

3.拓展

3.1 关键宏定义开关及启用方式

代码中涉及的核心宏定义开关及作用如下:

  1. CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS

    • 作用:启用 mbedTLS 作为 ESP-TLS 的底层加密库(mbedTLS 是轻量级嵌入式加密库,支持 TLS 协议及各类加密算法)。
    • 启用方式:通过idf.py menuconfig进入配置界面,在Example Configuration中勾选 "Use mbedTLS with ESP-TLS"。
  2. CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT

    • 作用:控制客户端是否使用 TLS 1.3 协议。TLS 1.3 相比 TLS 1.2 安全性更高(强制前向保密、移除弱加密套件)、握手更快(1 个 RTT)。
    • 启用方式:在menuconfigExample Configuration中,找到 "SSL/TLS protocol version for client",选择 "TLS 1.3"。
  3. CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS

    • 作用:启用客户端 TLS 会话票证(Session Ticket)功能,实现会话复用。首次握手后,客户端存储服务器下发的加密票证,下次连接时直接提交票证,跳过密钥协商,减少延迟和资源消耗。
    • 启用方式:在menuconfigExample Configuration中勾选 "Enable client session tickets"。
  4. 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.输出结果及调试记录

注意:在调试的时候遇到过时间同步的问题,这个需要注意

相关推荐
迎風吹頭髮3 小时前
UNIX下C语言编程与实践41-UNIX 单线程 I/O 超时处理:终端方式、信号跳转方式与多路复用方式
c语言·php·unix
半桔3 小时前
【网络编程】揭秘 HTTPS 数据安全:加密方案与证书体系的协同防护
linux·网络·网络协议·http·https
心静财富之门3 小时前
【无标题】标签单击事件
开发语言·php
盛满暮色 风止何安4 小时前
网络安全设备 防火墙
服务器·网络·网络协议·计算机网络·安全·web安全·网络安全
weixin_4462608513 小时前
快速构建网站的利器——Symfony PHP框架
开发语言·php·symfony
朝新_14 小时前
【EE初阶 - 网络原理】网络通信
java·开发语言·网络·php·javaee
-dcr15 小时前
22.Nginx 服务器 LNMP项目
运维·服务器·nginx·php·lnmp
hanxiaozhang201817 小时前
Netty面试重点-1
网络·网络协议·面试·netty
锋风Fengfeng18 小时前
基于Binder的4种RPC调用
网络协议·rpc·binder