ESP32开发入门(七):HTTP开发实践
一、HTTP协议基础
1.1 什么是HTTP?
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络协议,用于从服务器传输超文本到本地浏览器。它是一种无状态的请求/响应协议,工作在客户端-服务器计算模型中。
1.2 HTTP的工作原理
HTTP协议基于请求-响应模型,主要包含以下组件:
-
客户端(Client):发送HTTP请求(如浏览器、ESP32等设备)
-
服务器(Server):接收请求并返回响应
-
请求方法:GET、POST、PUT、DELETE等
-
状态码:200(成功)、404(未找到)、500(服务器错误)等
ESP32设备(客户端) --HTTP请求--> Web服务器 <--HTTP响应-- 浏览器或其他客户端
1.3 HTTP的核心特性
-
简单快速:基于文本的简单协议
-
无连接:每次连接只处理一个请求
-
无状态:协议不保留之前的请求信息
-
灵活:可以传输任意类型的数据
-
支持多种请求方法:满足不同场景需求
1.4 HTTP在物联网中的应用
-
设备数据上报:向服务器发送传感器数据
-
远程配置:从服务器获取设备配置
-
固件升级:通过HTTP下载固件包
-
Web控制界面:提供设备管理页面
-
API交互:与其他系统集成
二、ESP32-S3 HTTP通信程序 (FreeRTOS + Arduino框架)
下面是一个基于ESP32-S3的HTTP通信程序,使用FreeRTOS和Arduino框架实现。这个程序包含HTTP客户端功能,可以向服务器发送GET和POST请求。
#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// 服务器配置
const char* serverUrl = "http://你的服务器地址:端口/api/data"; // 示例:"http://192.168.1.100:3000/api/data"
// FreeRTOS任务句柄
TaskHandle_t httpTaskHandle = NULL;
TaskHandle_t wifiTaskHandle = NULL;
// 连接WiFi函数
void connectToWiFi() {Serial.println();Serial.print("正在连接WiFi: ");Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {vTaskDelay(500 / portTICK_PERIOD_MS);Serial.print(".");}
Serial.println("");Serial.println("WiFi已连接");Serial.print("IP地址: ");Serial.println(WiFi.localIP());
}
// 发送HTTP GET请求
void sendHttpGetRequest() {if (WiFi.status() == WL_CONNECTED) {HTTPClient http;Serial.print("发送GET请求到: ");Serial.println(serverUrl);http.begin(serverUrl);int httpCode = http.GET();if (httpCode > 0) {Serial.printf("HTTP响应码: %d\n", httpCode);if (httpCode == HTTP_CODE_OK) {String payload = http.getString();Serial.println("服务器响应:");Serial.println(payload);}} else {Serial.printf("GET请求失败, 错误: %s\n", http.errorToString(httpCode).c_str());}http.end();} else {Serial.println("WiFi未连接,无法发送请求");}
}
// 发送HTTP POST请求
void sendHttpPostRequest() {if (WiFi.status() == WL_CONNECTED) {HTTPClient http;Serial.print("发送POST请求到: ");Serial.println(serverUrl);http.begin(serverUrl);http.addHeader("Content-Type", "application/json");// 创建JSON格式的POST数据String httpRequestData = "{\"deviceId\":\"ESP32-S3\",\"temperature\":25.5,\"humidity\":60}";int httpCode = http.POST(httpRequestData);if (httpCode > 0) {Serial.printf("HTTP响应码: %d\n", httpCode);if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {String payload = http.getString();Serial.println("服务器响应:");Serial.println(payload);}} else {Serial.printf("POST请求失败, 错误: %s\n", http.errorToString(httpCode).c_str());}http.end();} else {Serial.println("WiFi未连接,无法发送请求");}
}
// HTTP任务函数
void httpTask(void *pvParameters) {while (1) {// 每隔10秒发送一次请求static uint32_t lastRequestTime = 0;uint32_t now = millis();if (now - lastRequestTime > 10000) {lastRequestTime = now;// 交替发送GET和POST请求static bool sendGet = true;if (sendGet) {sendHttpGetRequest();} else {sendHttpPostRequest();}sendGet = !sendGet;}vTaskDelay(100 / portTICK_PERIOD_MS);}
}
// WiFi监控任务函数
void wifiMonitorTask(void *pvParameters) {while (1) {if (WiFi.status() != WL_CONNECTED) {Serial.println("WiFi连接丢失,尝试重新连接...");connectToWiFi();}vTaskDelay(10000 / portTICK_PERIOD_MS); // 每10秒检查一次}
}
void setup() {Serial.begin(115200);// 初始化WiFi连接connectToWiFi();// 创建HTTP任务xTaskCreatePinnedToCore(httpTask, // 任务函数"HTTP Task", // 任务名称8192, // 堆栈大小NULL, // 参数1, // 优先级&httpTaskHandle, // 任务句柄1 // 运行在核心1上);// 创建WiFi监控任务xTaskCreatePinnedToCore(wifiMonitorTask, // 任务函数"WiFi Task", // 任务名称4096, // 堆栈大小NULL, // 参数1, // 优先级&wifiTaskHandle, // 任务句柄0 // 运行在核心0上);
}
void loop() {// 主循环为空,所有功能由FreeRTOS任务处理vTaskDelay(1000 / portTICK_PERIOD_MS);
}
2.1 代码说明
-
WiFi连接:
-
使用
WiFi.begin()
连接到指定的WiFi网络 -
单独的WiFi监控任务持续检查连接状态并在断开时重新连接
-
-
HTTP功能:
-
使用HTTPClient库实现HTTP协议
-
支持GET和POST请求
-
POST请求发送JSON格式数据
-
自动处理HTTP响应
-
-
FreeRTOS集成:
-
创建了两个任务:一个用于HTTP通信,一个用于WiFi监控
-
任务运行在不同的核心上以提高效率
-
使用
vTaskDelay()
代替delay()
以确保不阻塞其他任务
-
-
多任务处理:
-
HTTP任务负责定期发送HTTP请求
-
WiFi任务持续监控网络连接状态
-
2.2 使用说明
-
修改
ssid
和password
为你自己的WiFi配置 -
修改
serverUrl
为你的服务器地址和API端点 -
根据需要调整POST请求的内容和格式
-
请求频率可以在
httpTask
函数中调整
2.3 所需库
-
WiFi.h (Arduino ESP32核心自带)
-
HTTPClient (Arduino ESP32核心自带)
三、HTTP验证步骤 - 搭建Node.js服务器
为了验证ESP32的HTTP功能,我们可以使用Node.js搭建一个简单的服务器,接收ESP32的请求并返回响应。
下面我将详细介绍如何搭建一个完整的Node.js服务器,并将HTML页面数据整合到server.js文件中,以便于ESP32通过HTTP协议与服务器进行通信。
注意:若你电脑没安装node,请自行百度安装,网上教程较多,这里就不赘述了。
3.1 创建Node.js服务器
-
新建一个文件夹作为项目目录
-
在该目录下创建
server.js
文件,内容如下:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
// 中间件
app.use(bodyParser.json());
// 存储接收到的数据
let receivedData = [];
// HTML页面内容
const htmlPage = `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ESP32 数据监控</title><style>body {font-family: Arial, sans-serif;margin: 20px;}.data-container {margin-top: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 5px;background-color: #f9f9f9;}button {padding: 10px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}.data-item {margin-bottom: 10px;padding: 10px;border-bottom: 1px solid #eee;}.timestamp {color: #666;font-size: 0.9em;}</style>
</head>
<body><h1>ESP32 数据监控</h1><button id="refreshBtn">刷新数据</button><div class="data-container"><h2>最新上报数据 (共<span id="dataCount">0</span>条)</h2><div id="dataDisplay"><p>暂无数据...</p></div></div>
<script>const refreshBtn = document.getElementById('refreshBtn');const dataDisplay = document.getElementById('dataDisplay');const dataCount = document.getElementById('dataCount');// 格式化数据显示function formatData(data) {if (data.receivedData && data.receivedData.length > 0) {return data.receivedData.map(item => \`<div class="data-item"><div><strong>设备ID:</strong> \${item.data.deviceId || '未知'}</div><div><strong>温度:</strong> \${item.data.temperature || 'N/A'}°C</div><div><strong>湿度:</strong> \${item.data.humidity || 'N/A'}%</div><div class="timestamp">\${new Date(item.timestamp).toLocaleString()}</div></div>\`).join('');}return '<p>暂无数据...</p>';}// 获取数据函数async function fetchData() {try {const response = await fetch('/api/data');const data = await response.json();dataCount.textContent = data.receivedData ? data.receivedData.length : 0;dataDisplay.innerHTML = formatData(data);} catch (error) {dataDisplay.innerHTML = \`<p style="color:red;">获取数据失败: \${error.message}</p>\`;}}// 初始加载数据document.addEventListener('DOMContentLoaded', fetchData);// 按钮点击事件refreshBtn.addEventListener('click', fetchData);// 每5秒自动刷新setInterval(fetchData, 5000);</script>
</body>
</html>
`;
// 首页路由 - 返回HTML页面
app.get('/', (req, res) => {res.send(htmlPage);
});
// GET请求处理 - 获取所有数据
app.get('/api/data', (req, res) => {console.log('收到GET请求');res.status(200).json({message: '数据获取成功',receivedData: receivedData,timestamp: new Date().toISOString()});
});
// POST请求处理 - 接收ESP32数据
app.post('/api/data', (req, res) => {console.log('收到POST请求:', req.body);// 验证数据if (!req.body.deviceId) {return res.status(400).json({error: '缺少必要字段: deviceId'});}// 存储数据receivedData.push({data: req.body,timestamp: new Date().toISOString()});// 限制存储的数据量if (receivedData.length > 50) {receivedData = receivedData.slice(-50);}res.status(201).json({message: '数据接收成功',yourData: req.body});
});
// 清空数据接口
app.delete('/api/data', (req, res) => {receivedData = [];res.status(200).json({message: '所有数据已清空'});
});
// 启动服务器
app.listen(port, () => {console.log(`服务器运行在 http://localhost:${port}`);console.log(`API端点:`);console.log(`GET / - 查看数据监控页面`);console.log(`GET /api/data - 获取所有接收到的数据`);console.log(`POST /api/data - 接收ESP32发送的数据`);console.log(`DELETE /api/data - 清空所有数据`);
});
3.1.1 代码详细说明
3.1.1.1 初始化设置
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
-
引入Express框架和body-parser中间件
-
创建Express应用实例
-
设置服务器端口为3000
3.1.1.2 数据存储
let receivedData = [];
-
使用一个数组来存储ESP32发来的所有数据
-
每个数据项包含原始数据和接收时间戳
3.1.1.3 HTML页面整合
const htmlPage = `...`;
-
将完整的HTML页面内容作为模板字符串存储在变量中
-
包含CSS样式和内联JavaScript
-
使用ES6模板字符串语法方便插入变量
3.1.1.4 路由处理
-
首页路由:
app.get('/', (req, res) => {res.send(htmlPage); });
-
处理根路径请求
-
直接返回HTML页面内容
-
-
GET API接口:
app.get('/api/data', (req, res) => {res.json({message: '数据获取成功',receivedData: receivedData,timestamp: new Date().toISOString()}); });
-
返回所有存储的数据
-
包含状态信息和时间戳
-
-
POST API接口:
app.post('/api/data', (req, res) => {// 数据验证和存储res.status(201).json({message: '数据接收成功',yourData: req.body}); });
-
接收ESP32发来的JSON数据
-
验证必要字段
-
存储数据并返回确认
-
-
DELETE API接口:
app.delete('/api/data', (req, res) => {receivedData = [];res.json({ message: '所有数据已清空' }); });
-
清空存储的数据
-
用于测试和调试
-
3.1.1.5 前端JavaScript功能
// 格式化数据显示
function formatData(data) {// 将JSON数据转换为HTML显示
}
// 获取数据函数
async function fetchData() {// 从/api/data获取数据并更新页面
}
// 事件监听和自动刷新
document.addEventListener('DOMContentLoaded', fetchData);
refreshBtn.addEventListener('click', fetchData);
setInterval(fetchData, 5000);
-
使用Fetch API获取数据
-
动态更新页面内容
-
自动刷新和手动刷新功能
-
数据格式化显示
3.2 安装依赖
在项目目录下运行以下命令安装必要的依赖:
npm init -y
npm install express body-parser
3.3 启动服务器
node server.js
服务器启动后,你将在控制台看到:
服务器运行在 http://localhost:3000
现在你可以通过浏览器访问http://localhost:3000
来查看ESP32上报的数据。
3.4 验证步骤
-
确保你的PC和ESP32在同一个局域网
-
修改ESP32代码中的
serverUrl
为你的PC的IP地址和端口(如http://192.168.1.100:3000/api/data
) -
上传ESP32代码并打开串口监视器
-
在浏览器中访问
http://localhost:3000
-
观察串口输出和网页显示的数据
3.6 预期结果
-
串口输出:
发送GET请求到: http://192.168.1.100:3000/api/data HTTP响应码: 200 服务器响应: {"message":"Hello from Node.js server!","receivedData":[...],"timestamp":"..."} 发送POST请求到: http://192.168.1.100:3000/api/data HTTP响应码: 201 服务器响应: {"message":"Data received successfully","yourData":{"deviceId":"ESP32-S3","temperature":25.5,"humidity":60}}
-
网页显示:
最新上报数据 {"message": "Hello from Node.js server!","receivedData": [{"data": {"deviceId": "ESP32-S3","temperature": 25.5,"humidity": 60},"timestamp": "..."}],"timestamp": "..." }
四、实际项目应用示例
4.1 环境监测系统
功能设计:
-
定期上报温湿度数据
-
从服务器获取配置参数
-
实现固件升级检查
void checkForUpdates() {HTTPClient http;http.begin("http://yourserver.com/api/update");int httpCode = http.GET();if (httpCode == HTTP_CODE_OK) {String payload = http.getString();DynamicJsonDocument doc(1024);deserializeJson(doc, payload);if (doc["available"] == true) {String newVersion = doc["version"];String firmwareUrl = doc["url"];if (newVersion != currentFirmwareVersion) {startFirmwareUpdate(firmwareUrl);}}}http.end();
}
4.2 远程控制面板
功能设计:
-
提供Web控制界面
-
实现设备状态实时显示
-
支持多设备管理
void handleRoot() {String html = "<html><body>";html += "<h1>ESP32 Control Panel</h1>";html += "<p>Temperature: " + String(readTemperature()) + "°C</p>";html += "<p>Humidity: " + String(readHumidity()) + "%</p>";html += "<form method='post' action='/control'>";html += "<button name='led' value='on'>Turn LED On</button>";html += "<button name='led' value='off'>Turn LED Off</button>";html += "</form>";html += "</body></html>";server.send(200, "text/html", html);
}
五、HTTP最佳实践与优化
-
安全考虑:
-
使用HTTPS替代HTTP
-
实现API密钥验证
-
限制请求频率
-
-
性能优化:
-
复用HTTPClient对象
-
减少不必要的头信息
-
使用连接池
-
-
错误处理:
-
实现自动重试机制
-
添加超时设置
-
记录错误日志
-
-
数据格式:
-
使用JSON进行数据交换
-
压缩大数据量
-
分页获取大量数据
-
六、常见HTTP服务器选择
-
本地测试:
-
Node.js + Express
-
Python Flask
-
PHP内置服务器
-
-
生产环境:
-
Nginx
-
Apache
-
IIS
-
-
云服务:
-
AWS API Gateway
-
阿里云API网关
-
腾讯云API网关
-
七、总结与扩展
HTTP作为互联网的基础协议,与ESP32的结合为物联网设备提供了简单可靠的数据通信方案,相对于上一篇MQTT协议,HTTP协议的开发和验证更为简单,若你对MQTT开发感兴趣,可查看ESP32开发入门(六):MQTT开发实践。掌握HTTP开发后,您可以进一步:
-
研究HTTPS安全连接
-
学习WebSocket实现实时通信
-
探索RESTful API设计
-
了解gRPC等高效协议
通过本篇教程,您应该已经掌握了ESP32上HTTP开发的核心知识。实际项目中,建议从简单的原型开始,逐步增加功能复杂度,并始终考虑安全性和性能问题。