当前位置: 首页 > article >正文

【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由以下组成:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My HTML</title>
</head>
<body>
<h1>This is a title</h1>
<p>This is a paragraph</p>
</body>
</html>
  • <!DOCTYPE html>:声明这时一个HTML文档;
  • <html></html>:HTML内容;
  • <head></head>:头部内容;
  • <body></body>:页面内容。

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 <string.h>

#define TAG "app"

static httpd_handle_t http_server;

static const char index_html[] = " \
<!DOCTYPE html> \
<html> \
    <head> \
        <meta charset=\"utf-8\"> \
        <title>index</title> \
    </head> \
\
    <body> \
        <h1>Hello from ESP32</h1> \
        <form action=\"/hello\" method=\"post\"> \
            <label for=\"name\">What's your name:</label> \
            <input type=\"text\" id=\"name\" name=\"name\" required> \
            <input type=\"submit\" value=\"OK\"></button> \
        </form> \
    </body> \
</html> \
";

static const char hello_html_template[] = " \
<!DOCTYPE html> \
<html> \
    <head> \
        <meta charset=\"utf-8\"> \
        <title>hello</title> \
    </head> \
\
    <body> \
        <h1>Oh, Hello %s</h1> \
    </body> \
\
</html> \
";


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页面,如果你用的是浏览器请求的话,就会有自动解析并显示画面。

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

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

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

 


http://www.kler.cn/a/542779.html

相关文章:

  • SDKMAN! 的英文全称是 Software Development Kit Manager(软件开发工具包管理器)
  • 51单片机(国信长天)矩阵键盘的基本操作
  • 本地基于GGUF部署的DeepSeek实现轻量级调优之二:检索增强生成(RAG)
  • Spring依赖注入方式
  • 什么是矩阵账号?如何做矩阵账号运营?
  • 基于YOLOv8+PyQt5的目标检测系统(环境配置+数据集+Python源码+PyQt5界面)——持续更新中
  • Python基于 Flask 创建简单Web服务并接收文件
  • 头条百度批量采集软件说明文档
  • 2021版小程序开发5——小程序项目开发实践(2)-完
  • Harmony OS和Android 区别
  • 实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
  • C/C++混合读入cin与scanf问题
  • Go1.24 strings包增加的新方法
  • java安全中的类加载
  • 【Apache Paimon】-- 作为一名小白,如何系统地学习 Apache paimon?
  • Unity使用iTextSharp导出PDF-05页面事件
  • RESTful API 和 WebSocket 的区别
  • BootstrapBlazor Table组件 使用的注入 数据服务 实现类:使用 EF Core
  • Spring Cloud 07 - 分布式链路追踪APM
  • Acwing-基础算法课笔记之基础算法(差分)
  • Wiki文档转换为Word技术
  • 使用C语言实现MySQL数据库的增删改查操作指南
  • Java90道面试题
  • 利用邮件合并将Excel的信息转为Word(单个测试用例转Word)
  • 创建和使用 Python 虚拟环境(使用Python自带的venv模块)
  • Spring Boot 中加载多个 YAML 配置文件