4. HTTP客户端与服务器:从请求到RESTful API
这一章我们聊聊ESP32上的HTTP通信。说实话,HTTP是物联网设备最基础的“语言”之一——传感器上报数据、接收云端指令、甚至设备之间的互相调用,都离不开它。我个人习惯把HTTP比作“快递系统”:GET是取件,POST是寄件,JSON就是包裹里的货物清单。
好,咱们直接进入正题。
4.1 HTTP GET/POST请求:ESP32怎么“打电话”
ESP32要发起HTTP请求,最常用的库是HTTPClient。它封装了TCP连接、请求构造、响应解析这些底层操作,你只需要几行代码就能搞定。
先看一个GET请求的例子——从天气API获取数据:
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "你的WiFi名";
const char* password = "你的WiFi密码";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi已连接");
HTTPClient http;
http.begin("http://api.weather.com/v1/current?city=beijing");
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("响应数据:");
Serial.println(payload);
}
} else {
Serial.printf("请求失败,错误码:%d\n", httpCode);
}
http.end();
}
void loop() {}
POST请求稍微复杂一点,因为要带“包裹”——也就是请求体。我在项目中遇到过一个问题:传感器数据上报时,服务器总是返回400错误。排查了半天,发现是Content-Type没设置对。
HTTPClient http;
http.begin("http://api.example.com/data");
http.addHeader("Content-Type", "application/json");
String jsonData = "{\"temperature\":25.6,\"humidity\":60}";
int httpCode = http.POST(jsonData);
if (httpCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println("上报成功:" + response);
} else {
Serial.printf("上报失败,HTTP状态码:%d\n", httpCode);
}
http.end();
http.setTimeout(10000)延长超时。否则,设备会反复重试,把流量吃光。
4.2 JSON数据解析:从字符串到结构化数据
服务器返回的JSON字符串,说白了就是一串文本。要提取里面的字段,得用解析库。ESP32上最流行的是ArduinoJson——轻量、高效,而且API设计得很直观。
安装库:在Arduino IDE的库管理器里搜索“ArduinoJson by Benoit Blanchon”,安装最新版。
解析一个典型的JSON响应:
#include <ArduinoJson.h>
// 假设这是服务器返回的JSON
String json = "{\"status\":\"ok\",\"data\":{\"temp\":28.5,\"humidity\":65,\"timestamp\":\"2025-03-15T10:30:00Z\"}}";
// 分配一个静态JSON文档(根据数据大小调整容量)
StaticJsonDocument<200> doc;
// 反序列化
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.print("JSON解析失败:");
Serial.println(error.c_str());
return;
}
// 提取数据
const char* status = doc["status"]; // "ok"
float temp = doc["data"]["temp"]; // 28.5
int humidity = doc["data"]["humidity"]; // 65
const char* timestamp = doc["data"]["timestamp"]; // "2025-03-15T10:30:00Z"
Serial.printf("温度:%.1f°C,湿度:%d%%\n", temp, humidity);
StaticJsonDocument<256>起步,如果不够就换DynamicJsonDocument。但注意——动态分配会碎片化堆内存,长期运行的设备建议用静态文档。
构建JSON发送给服务器也很简单:
StaticJsonDocument<128> doc;
doc["device_id"] = "ESP32_001";
doc["temperature"] = 25.6;
doc["humidity"] = 60;
String output;
serializeJson(doc, output);
Serial.println("要发送的JSON:");
Serial.println(output);
// 然后通过HTTP POST发送output
4.3 ESP32作为简易Web服务器:让浏览器直接访问设备
有时候,你不想经过云平台,就想让手机浏览器直接连上ESP32看数据。这时候,ESP32可以变身成一个Web服务器。
ESP32的WebServer库用起来非常顺手。看一个最简单的例子——返回传感器数据页面:
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
void handleRoot() {
String html = "<html><body>";
html += "<h1>ESP32 传感器数据</h1>";
html += "<p>温度:25.6°C</p>";
html += "<p>湿度:60%</p>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
WiFi.begin("你的WiFi名", "你的WiFi密码");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("IP地址:");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.begin();
Serial.println("HTTP服务器已启动");
}
void loop() {
server.handleClient();
}
在浏览器输入ESP32的IP地址,就能看到那个简陋但能用的页面了。你想想看,这其实就是物联网设备本地调试的标配——不用依赖任何云服务。
server.on("/", handler)),然后在loop()里不断调用server.handleClient()。它不会阻塞主循环,所以你可以同时做传感器采样、LED控制等其他任务。
4.4 RESTful API设计:给设备定一套“规矩”
RESTful API,说白了就是一套大家都遵守的接口规范。它用HTTP方法(GET/POST/PUT/DELETE)对应CRUD操作(查/增/改/删),用URL路径表示资源。
我在一个智能家居项目里设计过这样的API:
| HTTP方法 | URL路径 | 功能 | 请求体示例 |
|---|---|---|---|
| GET | /api/sensors | 获取所有传感器数据 | 无 |
| GET | /api/sensors/temperature | 获取温度数据 | 无 |
| POST | /api/actuators/led | 控制LED开关 | {"state": "on"} |
| PUT | /api/config/wifi | 更新WiFi配置 | {"ssid":"...","password":"..."} |
| DELETE | /api/schedules/1 | 删除定时任务 | 无 |
在ESP32上实现RESTful API,其实就是把WebServer的路由和HTTP方法对应起来:
server.on("/api/sensors", HTTP_GET, []() {
StaticJsonDocument<256> doc;
doc["temperature"] = 25.6;
doc["humidity"] = 60;
doc["light"] = 1200;
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
});
server.on("/api/actuators/led", HTTP_POST, []() {
String body = server.arg("plain");
StaticJsonDocument<128> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
return;
}
const char* state = doc["state"];
if (strcmp(state, "on") == 0) {
digitalWrite(LED_BUILTIN, HIGH);
server.send(200, "application/json", "{\"status\":\"ok\",\"led\":\"on\"}");
} else if (strcmp(state, "off") == 0) {
digitalWrite(LED_BUILTIN, LOW);
server.send(200, "application/json", "{\"status\":\"ok\",\"led\":\"off\"}");
} else {
server.send(400, "application/json", "{\"error\":\"Invalid state\"}");
}
});
server.arg("plain")获取原始数据。如果你用server.arg("name"),那是获取URL编码的表单参数,不是JSON体。这个区别我当年搞混过好几次。
设计RESTful API时,有几个原则值得记住:
- 资源用名词,不用动词——
/api/sensors而不是/api/getSensors - 版本控制放路径——
/api/v1/sensors方便后续升级 - 状态码要准确——200表示成功,201表示创建成功,400表示客户端错误,500表示服务器错误
- 响应格式统一——成功返回
{"status":"ok","data":...},失败返回{"status":"error","message":"..."}
下面这张图总结了本章的核心知识体系:
嗯,到这里,HTTP客户端与服务器的核心内容就讲完了。你可能会问:这些代码看起来都不难,但实际项目里会遇到什么问题?
我分享几个真实教训:
- WiFi断连处理——ESP32的WiFi连接不是永久的。我在一个户外项目里,设备每隔几小时就会断连一次。解决方案是在HTTP请求前检查
WiFi.status(),如果断连就自动重连。 - JSON缓冲区溢出——有一次服务器返回的数据比预期大了很多,
StaticJsonDocument直接崩溃。从那以后,我都在解析前先检查payload.length(),超过阈值就改用DynamicJsonDocument。 - 并发请求处理——WebServer默认只能处理一个请求。如果客户端连续发多个请求,后面的会排队。对于需要高并发的场景,可以考虑用
AsyncWebServer库。
这一章的内容,说白了就是让ESP32既能“说”HTTP(客户端),也能“听”HTTP(服务器)。掌握了这些,你的设备就不再是一个孤立的单片机,而是物联网世界里一个能独立通信的节点。