【ESP32】ESP-IDF开发 | WiFi开发 | HTTP服务器

1. 简介

1.1 HTTP

        HTTP(Hyper Text Transfer Protocol),全称超文本传输协议,用于从网络服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还能确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本、图形等)。HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器(C/S)模型。HTTP是一个无状态的协议,基于TCP协议传输数据,默认使用端口80

1.1.1 请求

        HTTP把请求分成多种类型,其中最常用的是GET请求和POST请求

1. GET请求

        GET请求一般用于信息的获取,如访问网站使用的就是GET请求。GET请求仅仅只是获取资源信息,就像数据库查询一样,不会修改、增加数据,不会影响资源的状态。如果我们想在请求资源的同时附带数据,那么这些数据会被显式地放在请求URL上面

2. POST请求

        POST请求则表示可能会修改服务器上的资源,GET请求能做的,POST请求也能做。但最大的区别是请求参数的存放位置,POST请求会把参数隐式地放在请求报文中,所以对于敏感参数如账号密码等,会是更推荐的。

1.2 HTML

        HTML(HyperText Markup Language),全称超文本标记语言,是一种用于创建网页的标准标记语言。使用 HTML ,可以建立自己的 WEB 站点,HTML 运行在浏览器上,由浏览器来解析。

        最基础的HTML由以下组成:





My HTML


This is a title

This is a paragraph

  • :声明这时一个HTML文档;
  • :HTML内容;
  • :头部内容;
  • :页面内容。

2. 例程

        这个例程会在ESP32上面搭建一个简单的HTTP服务器,供局域网中的设备访问,包含基本的GET和POST请求演示。

2.1 函数API 

2.1.1 启动HTTP服务器

esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
  • handle:HTTP服务器句柄;
  • config:配置参数。 
typedef struct httpd_config {
    unsigned    task_priority;
    size_t      stack_size;
    BaseType_t  core_id;
    uint32_t    task_caps;
    uint16_t    server_port;
    uint16_t    ctrl_port;
    uint16_t    max_open_sockets;
    uint16_t    max_uri_handlers;
    uint16_t    max_resp_headers;
    uint16_t    backlog_conn;
    bool        lru_purge_enable;
    uint16_t    recv_wait_timeout;
    uint16_t    send_wait_timeout;
    void * global_user_ctx;
    httpd_free_ctx_fn_t global_user_ctx_free_fn;
    void * global_transport_ctx;
    httpd_free_ctx_fn_t global_transport_ctx_free_fn;
    bool enable_so_linger;
    int linger_timeout;
    bool keep_alive_enable;
    int keep_alive_idle;
    int keep_alive_interval;
    int keep_alive_count;
    httpd_open_func_t open_fn;
    httpd_close_func_t close_fn;
    httpd_uri_match_func_t uri_match_fn;
} httpd_config_t;

        配置参数比较多,ESP-IDF也提供了HTTPD_DEFAULT_CONFIG宏来初始化默认配置。如果要自定义,可以关注几个比较常用的:

  • stack_size:HTTP服务器任务的栈空间;
  • server_port:服务器端口,默认是80;
  • max_open_sockets:最大可开启socket,即可以连接的客户端数量;
  • max_uri_handlers:最大URI句柄数量;
  • recv_wait_timeout:接收超时时间;
  • send_wait_timeout:发送超时时间;
  • keep_alive_enable:网页保活使能;
  • keep_alive_idle:保活空闲时间;
  • keep_alive_interval:保活间隔时间;
  • keep_alive_count:保活包失败重传次数。

2.1.2 注册URI处理

esp_err_t httpd_register_uri_handler(httpd_handle_t handle, const httpd_uri_t *uri_handler);
  • handle:HTTP句柄;
  • uri_handler:URI处理结构体。
typedef struct httpd_uri {
    const char       *uri;
    httpd_method_t    method;
    esp_err_t (*handler)(httpd_req_t *r);
    void *user_ctx;
} httpd_uri_t;
  • uri:URI;
  • method:请求类型,格式如HTTP_XXX;
  • handler:处理函数;
  • user_ctx:用户上下文。

2.1.3 获取请求头字段内容长度

size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *field);
  • r:HTTP请求句柄;
  • field:字段名。

2.1.4 获取请求头字段内容

esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *val, size_t val_size);
  • r:HTTP请求句柄;
  • field:字段名;
  • val:输出数组;
  • val_size:数组长度。

2.1.5 获取URL参数长度

size_t httpd_req_get_url_query_len(httpd_req_t *r)
  • r:HTTP句柄。

2.1.6 获取URL参数

esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
  • r:HTTP句柄;
  • buf:输出数组;
  • buf_len:数组长度。

2.1.7 发送响应

esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len);
esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len);
static inline esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *str);
static inline esp_err_t httpd_resp_sendstr_chunk(httpd_req_t *r, const char *str);

         发送响应有几个函数,send结尾的就是一次性把数据发送完;send后面接str的就是发送字符串,这样就不需要传长度;chunk结尾的就是可以多次发送数据,最后一定要发一个长度为0的包,表示发送完成。

2.1.8 接收请求内容

int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
  • r:HTTP句柄;
  • buf:输出数组;
  • buf_len:数组长度。

2.2 代码

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"
#include "esp_http_server.h"

#include 

#define TAG "app"

static httpd_handle_t http_server;

static const char index_html[] = " \
 \
 \
     \
         \
        index \
     \
\
     \
        

Hello from ESP32

\
\ \ \ \
\ \ \ "; static const char hello_html_template[] = " \ \ \ \ \ hello \ \ \ \

Oh, Hello %s

\ \ \ \ "; static esp_err_t index_get_handler(httpd_req_t *req) { /* 获取Host信息 */ size_t buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1; if (buf_len > 1) { char *buf = malloc(buf_len); if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) { ESP_LOGI(TAG, "Get request to host: %s", buf); } free(buf); } /* 回复数据包 */ httpd_resp_sendstr(req, index_html); return ESP_OK; } static const httpd_uri_t index_uri = { .uri = "/index", .method = HTTP_GET, .handler = index_get_handler, .user_ctx = NULL }; static esp_err_t hello_post_handler(httpd_req_t *req) { /* 获取Host信息 */ size_t buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1; if (buf_len > 1) { char *buf = malloc(buf_len); if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) { ESP_LOGI(TAG, "Post request to host: %s", buf); } free(buf); } /* 获取内容长度 */ int len = 0; { char *buf = malloc(128); memset(buf, 0, 128); if (httpd_req_get_hdr_value_str(req, "Content-Length", buf, 128) != ESP_OK) { ESP_LOGE(TAG, "Get content length failed"); return ESP_FAIL; } len = atoi(buf) + 1; free(buf); } /* 获取表单数据 */ char *buf = malloc(len); memset(buf, 0, len); if (httpd_req_recv(req, buf, len) <= 0) { ESP_LOGE(TAG, "Receive request content failed"); return ESP_FAIL; } if (strstr(buf, "name=") == NULL) { ESP_LOGE(TAG, "Can't found fleid \"name\""); free(buf); return ESP_FAIL; } /* 发送数据 */ char *hello_html = malloc(1024); snprintf(hello_html, 1024, hello_html_template, buf + strlen("name=")); httpd_resp_sendstr(req, hello_html); free(buf); free(hello_html); return ESP_OK; } static const httpd_uri_t hello_uri = { .uri = "/hello", .method = HTTP_POST, .handler = hello_post_handler, .user_ctx = NULL }; static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == IP_EVENT) { if (event_id == IP_EVENT_STA_GOT_IP) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); if (httpd_start(&http_server, &config) == ESP_OK) { httpd_register_uri_handler(http_server, &index_uri); httpd_register_uri_handler(http_server, &hello_uri); } ESP_LOGI(TAG, "HTTP server on port %d", config.server_port); } } else if (event_base == WIFI_EVENT) { if (event_id == WIFI_EVENT_STA_DISCONNECTED) { httpd_stop(http_server); ESP_LOGI(TAG, "HTTP server stopped"); } else if (event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } } } int app_main() { /* 初始化NVS */ esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); } /* 初始化WiFi协议栈 */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL)); wifi_config_t wifi_config = { .sta = { .ssid = "Your SSID", .password = "Your password", .threshold.authmode = WIFI_AUTH_WPA_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()); return 0; }

         WiFi基站模式和AP连接在之前的文章有讲过,这里不再赘述。

        AP连接成功后会启动HTTP服务器,我这里全部使用默认的配置,注册了两个URI处理,一个是“/index”,用来演示GET请求,获取主页;一个是“/hello”,用来演示POST请求。

        第一个URI处理函数,演示一下请求头字段的获取,一般先获取字段的长度,接着请求对应大小的堆内存,再copy数据到数组中。不建议在函数内直接定义数组,因为处理函数是在HTTP任务中调用的,这样做很容易导致栈溢出。最后就是返回HTML页面文本给客户端。

如果ESP32在接受请求时报413错误,在SDK的配置文件(sdkconfig)中,修改CONFIG_HTTPD_MAX_REQ_HDR_LEN配置,增大请求头的长度。

         这个URI处理会返回一个HTML页面,如果你用的是浏览器请求的话,就会有自动解析并显示画面。

【ESP32】ESP-IDF开发 | WiFi开发 | HTTP服务器_第1张图片

        这个HTML包含一个标题和一些表单控件,我们可以在文本框这里填写自己的名字,点击“OK”按钮,会向ESP32提交表单数据,其实就是向“/hello”这个URI发起POST请求。

        对于POST请求,在HTTP的请求头中会有一个“Content-Length”字段来描述数据包的大小。我们首先获取这个字段内容,然后去请求相应的内存空间,最后copy数据包数据到数组中;如果数据包非常大的话也可以多次获取。

        对于表单数据,一般都是以键值对的形式组成的,中间用等于号连接。接收到POST请求后需要返回对应的数据,这里就是HTML文件,我们把表单获取到的数据附到HTML文档中,显示的效果如下。

【ESP32】ESP-IDF开发 | WiFi开发 | HTTP服务器_第2张图片 

你可能感兴趣的:(ESP32,http,服务器,网络协议,单片机,mcu,c语言)