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.输出结果及调试记录

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

相关推荐
BingoGo6 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack6 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
Jony_2 天前
高可用移动网络连接
网络协议
chilix2 天前
Linux 跨网段路由转发配置
网络协议
JaguarJack2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php