【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文档中,显示的效果如下。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/966915.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

生成式聊天机器人 -- 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 -- 下

生成式聊天机器人 -- 基于Pytorch Global Attention 双向 GRU 实现的SeqToSeq模型 -- 下 训练Masked 损失单次训练过程迭代训练过程 测试贪心解码(Greedy decoding)算法实现对话函数 训练和测试模型完整代码 生成式聊天机器人 – 基于Pytorch Global Attention 双向 GRU 实…

DeepSeeek如何在Window本地部署

一、Ollama Ollama 是一个开源的本地化大语言模型&#xff08;LLM&#xff09;运行工具&#xff0c;专注于简化大模型在本地环境中的部署、管理和交互。它支持多种主流开源模型&#xff08;如 Llama 2、Mistral、Phi-2 等&#xff09;&#xff0c;并提供了命令行和 API 接口&am…

01-SDRAM控制器的设计——案例总概述

本教程重点▷▷▷ 存储器简介。 介绍 SDRAM 的工作原理。 详细讲解SDRAM 控制的Verilog 实现方法。 PLL IP和FIFO IP 的调用&#xff0c;计数器设计&#xff0c;按键边沿捕获&#xff0c;数码管控制。 完成SDRAM控制器应用的完整案例。 Signal Tap 调试方法。 准备工作▷…

实验5 配置OSPFv2验证

实验5 配置OSPFv2验证 1.实验目的 &#xff08;1&#xff09;OSPFv2 验证的类型和意义。 &#xff08;2&#xff09;配置基于区域的 OSPFv2 简单口令验证和 MD5 验证的方法。 &#xff08;3&#xff09;配置基于链路的 OSPFv2 简单口令验证和 MD5 验证的方法。 2.实验准备 配置…

Office/WPS接入DeepSeek等多个AI工具,开启办公新模式!

在现代职场中&#xff0c;Office办公套件已成为工作和学习的必备工具&#xff0c;其功能强大但复杂&#xff0c;熟练掌握需要系统的学习。为了简化操作&#xff0c;使每个人都能轻松使用各种功能&#xff0c;市场上涌现出各类办公插件。这些插件不仅提升了用户体验&#xff0c;…

基于STM32HAL库的万年历系统

目录 前言 项目分析 CubeMX配置 工程文件结构 App文件夹 Lib文件夹 库文件代码 myrtc.c myrtc.h oled库&字符库 knob.c knob.h 业务逻辑代码 task_main.c task_main.h 前言 本篇博客来做一个简易的万年历系统&#xff0c;需要用到旋转编码器和0.96寸OLED屏幕…

【Matlab优化算法-第14期】基于智能优化算法的VMD信号去噪项目实践

基于智能优化算法的VMD信号去噪项目实践 一、前言 在信号处理领域&#xff0c;噪声去除是一个关键问题&#xff0c;尤其是在处理含有高斯白噪声的复杂信号时。变分模态分解&#xff08;VMD&#xff09;作为一种新兴的信号分解方法&#xff0c;因其能够自适应地分解信号而受到…

蓝耘智算平台与DeepSeek R1模型:推动深度学习发展

公主请阅 前言何为DeepSeek R1DeepSeek R1 的特点DeepSeek R1 的应用领域DeepSeek R1 与其他模型的对比 何为蓝耘智算平台使用蓝耘智算平台深度使用DeepSeek R1代码解释&#xff1a;处理示例输入&#xff1a;输出结果&#xff1a; 前言 在深度学习领域&#xff0c;创新迭代日新…

5、大模型的记忆与缓存

文章目录 本节内容介绍记忆Mem0使用 mem0 实现长期记忆 缓存LangChain 中的缓存语义缓存 本节内容介绍 本节主要介绍大模型的缓存思路&#xff0c;通过使用常见的缓存技术&#xff0c;降低大模型的回复速度&#xff0c;下面介绍的是使用redis和mem0&#xff0c;当然redis的语义…

windows蓝牙驱动开发-调试及支持的HCI和事件

调试蓝牙配置文件驱动程序 开发蓝牙配置文件驱动程序时&#xff0c;可以使用驱动程序验证程序来协助其调试。 若要启用验证检查&#xff0c;必须为 Bthusb.sys 启用驱动程序验证程序。 如果不执行此操作&#xff0c;将禁用验证检查。 若要完全利用验证检查&#xff0c;请确保…

深度求索(DeepSeek)的AI革命:NLP、CV与智能应用的技术跃迁

Deepseek官网&#xff1a;DeepSeek 引言&#xff1a;AI技术浪潮中的深度求索 近年来&#xff0c;人工智能技术以指数级速度重塑全球产业格局。在这场技术革命中&#xff0c;深度求索&#xff08;DeepSeek&#xff09;凭借其前沿的算法研究、高效的工程化能力以及对垂直场景的…

xxl-job使用nginx代理https后,访问出现403异常问题解决

在nginx代理为https之前&#xff0c;xxl-job使用http访问是没有问题的&#xff0c;但是换为https后&#xff0c;访问就有以下报错&#xff1a; 很多接口都出现了403异常 DataTables warning: table idjob_list - Ajax error. For more information about this error, please s…

kafka 3.5.0 raft协议安装

前言 最近做项目&#xff0c;需要使用kafka进行通信&#xff0c;且只能使用kafka&#xff0c;笔者没有测试集群&#xff0c;就自己搭建了kafka集群&#xff0c;实际上笔者在很早之前就搭建了&#xff0c;因为当时还是zookeeper&#xff08;简称ZK&#xff09;注册元数据&#…

Python 鼠标轨迹 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

爬虫技巧汇总

一、UA大列表 USER_AGENT_LIST 是一个包含多个用户代理字符串的列表&#xff0c;用于模拟不同浏览器和设备的请求。以下是一些常见的用户代理字符串&#xff1a; USER_AGENT_LIST [Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; Hot Lingo 2.0),Mozilla…

Microsoft Word xml 字符非法解决

如图&#xff0c;word能正常打开&#xff0c;复制内容到另外一个word时候出错&#xff0c;显示&#xff1a; Microsoft Word很抱歉,无法打开文档,因为内容有问题。确定详细信息(D)详细信息xml 字符非法。位置&#xff1a;行&#xff1a;3&#xff0c;列&#xff1a;2439 解决…

现代神经网络QA(LeNet/AlexNet/VGG/NiN/GooleNet/ResNet)-----一篇搞懂

现代神经网络Q&A-----一篇搞懂 LeNet核心架构 经典卷积神经网络的包括&#xff1a; 带填充以保持分辨率的卷积层&#xff1b;非线性激活函数&#xff0c;如ReLU&#xff1b;汇聚层&#xff0c;如最大汇聚层。 pooling时&#xff0c;使用avg还是max&#xff1f; max&…

数据结构与算法(test2)

五、串 1. 串是由___零___个或___多____个字符组成的有限序列, 又称为___字符串________。 一般记为 S“a1a2.....an” (n > 0), 串中的字符数目n称为串的__长度_____&#xff0c;零个字符的串称为___空串_____. 定义中谈到的"有限"是指长度 n 是一个有限的数值…

Matplotlib基础01( 基本绘图函数/多图布局/图形嵌套/绘图属性)

Matplotlib基础 Matplotlib是一个用于绘制静态、动态和交互式图表的Python库&#xff0c;广泛应用于数据可视化领域。它是Python中最常用的绘图库之一&#xff0c;提供了多种功能&#xff0c;可以生成高质量的图表。 Matplotlib是数据分析、机器学习等领域数据可视化的重要工…

六种负载均衡算法

六种负载均衡算法对比&#xff1a;原理、优缺点及适用场景 负载均衡是分布式系统的核心技术之一&#xff0c;通过合理分配请求流量&#xff0c;确保服务器资源高效利用&#xff0c;提升系统的可用性和响应速度。不同的负载均衡算法适用于不同的场景&#xff0c;以下是六种常见…