6.TLS Request

如果要發送https POST或GET需要有證書來驗證伺服器,
因此需要新增證書及使用esp_tls.h的API來實現連線。

step 1

新增證書

cURL下載公開認可的發行CACERT.pem

step 2

包含非常多發行單位證書,因ESP32容量大小限制,只保留需要的部分。
這邊以Line Notify為例,網址是:https://notify-api.line.me/api/notify
可以看到證書如下

發行者為:GlobalSign
因此編輯cacert.pem只保留GlobalSign相關的證書
檔案大小從222KB縮小為10KB

step 3

到CMakeLists.txt中新增code把cacert.pem增加到二進制檔案中
檔案路徑修改成存放證書的路徑

target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/cacert.pem" TEXT)

step 4

官方API document

main新增引用esp_tls.h才能使用API

#include "esp_tls.h"

function外宣告證書起始和結束

extern const uint8_t cacert_pem_start[] asm("_binary_cacert_pem_start");
extern const uint8_t cacert_pem_end[]   asm("_binary_cacert_pem_end");

編碼轉換的urlencode function

char *urlencode(const char *str) {
    if (str == NULL) {
        return NULL;
    }

    const char *hex = "0123456789ABCDEF";
    size_t len = strlen(str);
    char *out = malloc(3 * len + 1);  // 假設每個字元都要轉換成百分比編碼,預先分配足夠的空間

    if (out == NULL) {
        // 記憶體分配失敗
        return NULL;
    }

    char *ptr = out;
    while (*str) {
        if (isalnum((unsigned char)*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~') {
            *ptr++ = *str;
        } else if (*str == ' ') {
            // 空格轉換為%20
            *ptr++ = '%';
            *ptr++ = '2';
            *ptr++ = '0';
        } else {
            *ptr++ = '%';
            *ptr++ = hex[*str >> 4];
            *ptr++ = hex[*str & 0xF];
        }
        str++;
    }

    *ptr = '\0';  // 字串結尾符號
    return out;  // return編碼後字串
}

Send Line Notify function

void send_line_notify(const char *message) {
    // 建立TLS設置結構
    esp_tls_cfg_t cfg = {
        .cacert_pem_buf  = cacert_pem_start,
        .cacert_pem_bytes = cacert_pem_end - cacert_pem_start,
    };
    // 使用urlencode編碼欲傳送的message
    char *encoded_message = urlencode(message);
    if (!encoded_message) {
        ESP_LOGE(TAG, "Failed to encode message!");
        return;
    }
    ESP_LOGI(TAG, "Encoded message: %s", encoded_message); // 顯示編碼後的訊息
    ESP_LOGI(TAG, "Encoded message length: %d", strlen(encoded_message)); // 顯示編碼後訊息的長度
    int content_length = strlen("message=") + strlen(encoded_message);
    ESP_LOGI(TAG, "Calculated content length: %d", content_length); // 顯示編碼後加上message=的訊息長度
    const char *LINE_TOKEN = ""; //替換成自己的TOKEN
    const char *WEB_SERVER_URL = "https://notify-api.line.me/api/notify";
    char REQUEST[1024];
    // 建立Request請求
    int request_length = snprintf(REQUEST, sizeof(REQUEST), 
        "POST /api/notify HTTP/1.1\r\n"
        "Host: notify-api.line.me\r\n"
        "Authorization: Bearer %s\r\n"
        "Content-Type: application/x-www-form-urlencoded\r\n"
        "Content-Length: %d\r\n\r\n"
        "message=%s",
        LINE_TOKEN, strlen("message=") + strlen(encoded_message), encoded_message);
    //檢查大小是否相同
    if (request_length >= sizeof(REQUEST)) {
        ESP_LOGE(TAG, "Request string buffer too small!");
        free(encoded_message);
        return;
    }
    ESP_LOGI(TAG, "HTTPS Request: %s", REQUEST); // 顯示完整HTTP Request請求

    char buf[512];
    int ret, len;

    // 初始化TLS
    esp_tls_t *tls = esp_tls_init();
    if (!tls) {
        ESP_LOGE(TAG, "Failed to allocate esp_tls handle!");
        return;
    }

    // 建立TLS連接
    if (esp_tls_conn_http_new_sync(WEB_SERVER_URL, &cfg, tls) == 1) {
        ESP_LOGI(TAG, "Connection established...");
    } else {
        ESP_LOGE(TAG, "Connection failed...");
        goto cleanup;
    }

    // 發送HTTP請求
    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));

    // 讀取HTTP回應
    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);
        // 顯示Response
        printf("%.*s", len, buf);
    } while (1);
    free(encoded_message); // 釋放編碼後字串
cleanup:
    // 銷毀TLS連線物件
    esp_tls_conn_destroy(tls);
}

備註:因Line Notify request body起始為 “message=”+編碼後訊息
因此Content-Length不能只有編碼後長度,要加上”message=”

最後在需要發送訊息的地方呼叫

send_line_notify("Subscribe /RFIDTrackingSystem/command Success");

step 5

完成,若有收到訊息,但Response為400、408等等,檢查傳送前的
文字編碼、編碼後長度、加上”message=”後的編碼長度、確認完整requests
長度可使用postman測試發送相同body內容,比較Content-Length

Visited 18 times, 1 visit(s) today

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *