4、HTTP服务器搭建:创建ESP32 Web服务器、处理路由、返回HTML页面
好,到了这一章,我们终于要让ESP32真正“开口说话”了。
前面几章我们把开发环境搭好、Wi-Fi连上、基本通信搞通,但说实话,那都是热身。从这章开始,ESP32会变成一个真正的Web服务器——你打开浏览器,输入它的IP地址,就能看到它返回的页面。这才是前端交互的第一步。
4.1 为什么要在ESP32上搭HTTP服务器?
你可能要问:ESP32这么小的MCU,跑HTTP服务器靠谱吗?
我刚开始做这个决定时也有点犹豫。但后来发现,ESP32的双核240MHz处理器加上4MB Flash,跑一个轻量级的HTTP服务器绰绰有余。说白了,我们不是要让它处理百万并发,而是让它能响应几个客户端的请求——这完全没问题。
在嵌入式设备上搭建HTTP服务器,有几个实实在在的好处:
- 零客户端依赖:任何设备只要有浏览器,就能访问ESP32的页面
- 调试方便:我在项目里经常用手机浏览器直接看传感器数据,不用装任何App
- 跨平台:Windows、macOS、Linux、Android、iOS,通吃
- 为WebSocket打基础:HTTP是WebSocket的“敲门砖”,握手阶段必须走HTTP
4.2 ESP32上的HTTP服务器方案选型
ESP32的官方SDK(ESP-IDF)里自带了一个HTTP服务器组件,叫esp_http_server。如果你用Arduino框架,也有对应的库。我个人更推荐用ESP-IDF原生方案,原因很简单——稳定、可控、文档全。
来看一下几个主流方案的对比:
| 方案 | 框架 | 内存占用 | 性能 | 适用场景 |
|---|---|---|---|---|
| esp_http_server | ESP-IDF | 低 (~30KB) | 高 | 生产级项目 |
| AsyncWebServer | Arduino | 中 (~50KB) | 中 | 快速原型 |
| WebServer库 | Arduino | 低 (~25KB) | 低 | 简单测试 |
嗯,这里要注意:如果你用Arduino的WebServer库,它是同步阻塞的——一个请求没处理完,其他请求就得排队等着。这在生产环境里是个大坑。我踩过这个坑,后来全换成esp_http_server了。
4.3 核心概念:URI、路由、Handler
在动手写代码之前,我们先理清三个概念。搞懂了它们,后面写代码就是填空。
- URI(统一资源标识符):就是浏览器地址栏里“/”后面的那串东西。比如
/、/sensor、/style.css - 路由(Route):把URI和对应的处理函数绑定起来。比如“当用户访问
/时,执行root_get_handler” - Handler(处理函数):真正干活儿的函数。它接收请求,生成响应,然后返回给客户端
说白了,路由就是“门牌号”,Handler就是“屋里的人”。你敲哪个门,对应的人出来应答。
4.4 实战:搭建第一个ESP32 Web服务器
好,我们直接上代码。下面这个例子会创建一个HTTP服务器,监听80端口,处理两个路由:/返回一个简单的HTML页面,/api/hello返回JSON数据。
#include <esp_http_server.h>
#include <esp_log.h>
static const char *TAG = "HTTP_SERVER";
/* Handler for GET / */
static esp_err_t root_get_handler(httpd_req_t *req)
{
const char resp[] = "<!DOCTYPE html><html><body>"
"<h1>Hello from ESP32!</h1>"
"<p>This is my first Web page.</p>"
"</body></html>";
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
/* Handler for GET /api/hello */
static esp_err_t hello_get_handler(httpd_req_t *req)
{
const char resp[] = "{\"message\":\"Hello from ESP32 API\"}";
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
/* Start the HTTP server */
httpd_handle_t start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 80;
config.max_uri_handlers = 10;
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start server");
return NULL;
}
/* Register URI handlers */
httpd_uri_t uri_root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &uri_root);
httpd_uri_t uri_hello = {
.uri = "/api/hello",
.method = HTTP_GET,
.handler = hello_get_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &uri_hello);
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
return server;
}
httpd_resp_set_type写成了httpd_resp_set_status,结果浏览器一直显示乱码。后来才发现,Content-Type没设对,浏览器不知道该怎么解析返回的数据。记住:HTML页面要设text/html,JSON要设application/json,CSS要设text/css。
4.5 返回完整的HTML页面
上面那个例子返回的HTML太简陋了。实际项目中,我们需要返回完整的页面——带CSS样式、带JavaScript脚本、甚至带图片。但ESP32的Flash空间有限,不能像PC服务器那样放几百个文件。怎么办?
我常用的做法有两种:
- 内嵌HTML:把整个页面写成字符串,直接编译进固件。适合小页面(几KB以内)
- SPIFFS文件系统:把HTML/CSS/JS文件上传到ESP32的Flash文件系统里,服务器从文件系统读取并返回。适合大页面
这一章我们先讲第一种——内嵌HTML。后面会有专门章节讲SPIFFS。
来看一个带样式的完整页面例子:
static const char* index_html =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>ESP32 Dashboard</title>"
"<style>"
"body { font-family: Arial; text-align: center; margin-top: 50px; background: #f0f8ff; }"
"h1 { color: #2c3e50; }"
".card { background: white; padding: 20px; margin: 20px auto; max-width: 400px; "
" border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"
"</style>"
"</head>"
"<body>"
"<h1>ESP32 Dashboard</h1>"
"<div class=\"card\">"
"<h2>System Info</h2>"
"<p>Chip: ESP32</p>"
"<p>Free Heap: ";
/* 在handler里拼接动态数据 */
static esp_err_t dashboard_get_handler(httpd_req_t *req)
{
char buf[1024];
int heap = esp_get_free_heap_size();
snprintf(buf, sizeof(buf),
"%s%d bytes</p></div></body></html>",
index_html, heap);
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, buf, strlen(buf));
return ESP_OK;
}
你看,我把静态的HTML模板拆成两部分:前半段是固定的样式和结构,后半段在handler里用snprintf把动态数据(比如空闲堆内存)拼进去。这样既保持了代码整洁,又能返回动态内容。
4.6 本章知识结构图
下面这张图把HTTP服务器的核心流程和本章涉及的关键点串起来了:
4.7 常见问题与避坑指南
写HTTP服务器时,有几个问题我几乎每次都会遇到。列出来给你参考:
- 端口被占用:如果Wi-Fi没连上就启动服务器,80端口可能被其他服务占用。我习惯在启动服务器前先检查Wi-Fi状态
- 内存不足:每个HTTP连接会占用约4KB的RAM。如果并发连接太多,ESP32可能崩溃。设置
max_open_sockets为5就够用了 - Content-Type设错:浏览器是根据Content-Type来解析响应的。设错了,页面可能显示乱码或空白
- 字符串太长:内嵌HTML时,字符串长度不要超过ESP32的栈空间。我一般把大字符串声明为
static const,放在Flash里
4.8 小结
这一章我们做了三件事:理解了HTTP服务器的核心概念、用esp_http_server搭建了第一个服务器、学会了返回带样式的HTML页面。你现在已经能让ESP32“说话”了——虽然只是简单的“Hello World”,但这是后面所有交互的基础。