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();
我曾经踩过的坑:ESP32的HTTPClient默认超时时间是5000ms。如果你的服务器响应慢(比如通过4G模块转发),记得调用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);
容量估算技巧:JSON文档大小 = 键名总长度 + 值总长度 + 嵌套层级开销。我一般用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地址,就能看到那个简陋但能用的页面了。你想想看,这其实就是物联网设备本地调试的标配——不用依赖任何云服务。

核心要点:WebServer库是事件驱动的。你注册路由(比如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\"}");
  }
});
注意:ESP32的WebServer在处理POST/PUT请求体时,需要用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":"..."}

下面这张图总结了本章的核心知识体系:

第4章:HTTP客户端与服务器 · 知识体系 ESP32 HTTP通信 HTTP客户端 GET请求 · POST请求 HTTPClient库 · 超时设置 JSON数据解析 ArduinoJson库 · 反序列化 静态/动态文档 · 容量估算 简易Web服务器 WebServer库 · 路由注册 事件驱动 · 非阻塞处理 RESTful API设计 资源路径 · HTTP方法映射 状态码 · 统一响应格式

嗯,到这里,HTTP客户端与服务器的核心内容就讲完了。你可能会问:这些代码看起来都不难,但实际项目里会遇到什么问题?

我分享几个真实教训:

  • WiFi断连处理——ESP32的WiFi连接不是永久的。我在一个户外项目里,设备每隔几小时就会断连一次。解决方案是在HTTP请求前检查WiFi.status(),如果断连就自动重连。
  • JSON缓冲区溢出——有一次服务器返回的数据比预期大了很多,StaticJsonDocument直接崩溃。从那以后,我都在解析前先检查payload.length(),超过阈值就改用DynamicJsonDocument。
  • 并发请求处理——WebServer默认只能处理一个请求。如果客户端连续发多个请求,后面的会排队。对于需要高并发的场景,可以考虑用AsyncWebServer库。
调试小技巧:开发阶段,我习惯在ESP32上同时开启Web服务器和串口输出。这样,既可以用浏览器测试API,又能在串口监视器里看到详细的调试日志。等调试完了,再把串口输出关掉,节省资源。

这一章的内容,说白了就是让ESP32既能“说”HTTP(客户端),也能“听”HTTP(服务器)。掌握了这些,你的设备就不再是一个孤立的单片机,而是物联网世界里一个能独立通信的节点。