项目中遇到关于https的应用,例程中只有关于https的get,没有post,原以为只需要简单改动一下就能使用,但是实际调试过程中,发现不能用。 现在记录一下,防止忘记。
原例程是参照https_request_example_main.c文件中
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(example_connect());
xTaskCreate(&https_get_task, "https_get_task", 8192, NULL, 5, NULL);
}
这个是主函数。其中的
ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
分别做了flash初始化,网络初始化,回调事件创建,以及网络连接。
如果已经建立了网络连接可以只调用ESP_ERROR_CHECK(esp_event_loop_create_default());
xTaskCreate(&https_get_task, “https_get_task”, 8192, NULL, 5, NULL);
这两个,就可以直接进行https的相关操作。
static void https_get_task(void *pvParameters)
{
char buf[512];
int ret, len;
while(1) {
esp_tls_cfg_t cfg = {
.crt_bundle_attach = esp_crt_bundle_attach,
};
struct esp_tls *tls = esp_tls_conn_http_new(WEB_URL, &cfg);
if(tls != NULL) {
ESP_LOGI(TAG, "Connection established...");
} else {
ESP_LOGE(TAG, "Connection failed...");
goto exit;
}
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%x", ret);
goto exit;
}
} while(written_bytes < strlen(REQUEST));
ESP_LOGI(TAG, "Reading HTTP response...");
do
{
len = sizeof(buf) - 1;
bzero(buf, 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;
if(ret < 0)
{
ESP_LOGE(TAG, "esp_tls_conn_read returned -0x%x", -ret);
break;
}
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]);
}
} while(1);
exit:
esp_tls_conn_delete(tls);
putchar('\n'); // JSON output doesn't have a newline at end
static int request_count;
ESP_LOGI(TAG, "Completed %d requests", ++request_count);
for(int countdown = 10; countdown >= 0; countdown--) {
ESP_LOGI(TAG, "%d...", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGI(TAG, "Starting again!");
}
}
https_get_task()函数中,esp_tls_conn_http_new()是通过url建立https的连接。esp_tls_conn_write()完成传输,即通过TCP协议层传输https的header
static const char *REQUEST = "GET " WEB_URL " HTTP/1.0\r\n"
"Host: "WEB_SERVER"\r\n"
"User-Agent: esp-idf/1.0 esp32\r\n"
"\r\n";
这里的WEB_SERVER,WEB_URL是用define定义的。(有一点需要注意的是,这种定义方式在Visual Studio中的C/C++会报错。)
下面的ret = esp_tls_conn_write(tls,
REQUEST + written_bytes,
strlen(REQUEST) - written_bytes);
是循环将header传输到对应的服务器上。
写完以后,开始等待http的回复。
后面进行回复读取:
bzero(buf, sizeof(buf));
ret = esp_tls_conn_read(tls, (char *)buf, len);
bzero()类似于memset(),不过在http当中用的比较多。
但是上面的例程是进行https get请求的,若进行post请求呢?
大多数情况下,post请求都是需要上传参数的。不管是text,xml,还是json。
body应该怎么填的?一开始我是这样做的。
const char *REQUEST = "POST " WEB_URL " HTTP/1.0\r\n"
"Host: " WEB_SERVER "\r\n"
"User-Agent: Mozilla/5.0\r\n"
"Content-Type:application/json\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Connection:keep-alive\r\n"
"Content-Length:360\r\n"
"Accept:*/*\r\n"
"\r\n";
"{\r\n \"appId\": \"intelligent_cabinet\",\r\n \"requestId\": \"01831016-b18c-4a1e-a571\",\r\n \"version\": \"2.0\",\r\n \"timestamp\": \"1617325383000\",\r\n \"sign\": \"ZDJlNGJmZDQ4Y2MyOWNmOTU1ZDcyNTRkNzc3NDQwMzQwNzU1MGY2MQ==\"\r\n}";
格式是按照http的格式填充的,实际测试当中,发现body请求体并没有传到服务器。
后问了乐鑫的FAE,提示可以参考esp_http_client_example.c例程测试。
但是该例程当中也有不少的坑。
首先,这个例程当中包含多个请求方式,并且相当杂乱。
static void http_test_task(void *pvParameters)
{
http_rest_with_url();
http_rest_with_hostname_path();
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
http_auth_basic();
http_auth_basic_redirect();
#endif
http_auth_digest();
http_relative_redirect();
http_absolute_redirect();
https_with_url();
https_with_hostname_path();
http_redirect_to_https();
http_download_chunk();
http_perform_as_stream_reader();
https_async();
https_with_invalid_url();
http_native_request();
ESP_LOGI(TAG, "Finish http example");
vTaskDelete(NULL);
}
开始使用这个https_with_url();设置了一下,发现用不了,后面又调整了几次,终于发送成功了。
改的例程如下:
void https_with_url(void)
{
char *outJson=NULL;
char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
char request[512]={'\0'};
outJson=rebuild_json();
sprintf(request,"%s",outJson);
free(outJson);
esp_netif_init();
esp_event_loop_create_default();
esp_http_client_config_t config = {
.url ="https://daily-robot.ele.me/bdi.robot_scheduler/v2/openapi/cater/order/prepared",
.event_handler = http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_err_t err = esp_http_client_open(client, strlen(request));
printf("https_with_url request =%s\n",request);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
} else {
int wlen = esp_http_client_write(client, request, strlen(request));
if (wlen < 0) {
ESP_LOGE(TAG, "Write failed");
}
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
}
esp_http_client_cleanup(client);
}
http回复的可以在http_event_handler()函数中的
case HTTP_EVENT_ON_DATA:
后面打印一下:
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
/*
* Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
* However, event handler can also be used in case chunked encoding is used.
*/
if (!esp_http_client_is_chunked_response(evt->client)) {
// If user_data buffer is configured, copy the response into the buffer
if (evt->user_data) {
memcpy(evt->user_data + 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;
}
printf("output_buffer=%s\n",output_buffer);
break;
跟postman上面的测试结果一致,后面就可以添加自己的数据处理拉,完成。
源码在这里:乐鑫ESP32 http post请求源码修改