跳转至

HTTP/HTTPS协议应用开发实战

学习目标

完成本教程后,你将能够:

  • 理解HTTP协议的工作原理和请求/响应模型
  • 掌握HTTP方法(GET、POST、PUT、DELETE)的使用
  • 了解HTTPS加密通信的原理和证书验证
  • 使用ESP32实现HTTP客户端功能
  • 调用RESTful API获取和发送数据
  • 实现简单的Web服务器功能

前置要求

在开始本教程之前,你需要:

知识要求: - 了解TCP/IP协议基础知识 - 熟悉C语言编程 - 理解客户端-服务器架构 - 了解JSON数据格式

技能要求: - 能够使用Arduino IDE或ESP-IDF开发ESP32 - 会配置WiFi网络连接 - 了解基本的Web概念(URL、请求、响应)

准备工作

硬件准备

名称 数量 说明 参考链接
ESP32开发板 1 ESP32-DevKitC或类似型号 -
DHT11温湿度传感器 1 可选,用于数据采集 -
LED灯 1 用于演示Web控制 -
电阻 1 220Ω,LED限流电阻 -
Micro USB数据线 1 用于供电和程序下载 -

软件准备

  • 开发环境:Arduino IDE 2.0+ 或 ESP-IDF v4.4+
  • HTTP库:HTTPClient (Arduino内置) 或 esp_http_client (ESP-IDF)
  • 测试工具:Postman 或 curl命令行工具
  • API测试服务:JSONPlaceholder (https://jsonplaceholder.typicode.com)

环境配置

1. 安装Arduino IDE和ESP32支持

在Arduino IDE中添加ESP32开发板支持: - 打开 文件 -> 首选项 -> 附加开发板管理器网址 - 添加URL: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

2. 验证HTTP库

Arduino ESP32已内置HTTPClient库,无需额外安装。

3. 测试WiFi连接

确保ESP32能够连接到WiFi网络并访问互联网。

HTTP协议基础

什么是HTTP?

HTTP (HyperText Transfer Protocol) 是一种应用层协议,用于在Web上传输超文本和其他数据。

核心特点: - 无状态:每个请求独立,服务器不保存客户端状态 - 请求/响应模型:客户端发送请求,服务器返回响应 - 文本协议:使用可读的文本格式 - 基于TCP:运行在TCP协议之上(默认端口80) - 灵活扩展:支持多种内容类型和编码

HTTP请求方法

方法 说明 用途 幂等性
GET 获取资源 查询数据、下载文件
POST 创建资源 提交表单、上传数据
PUT 更新资源 完整更新数据
DELETE 删除资源 删除数据
PATCH 部分更新 修改部分字段
HEAD 获取头部 检查资源是否存在

HTTP状态码

类别 范围 说明 常见示例
信息响应 100-199 请求已接收,继续处理 100 Continue
成功响应 200-299 请求成功处理 200 OK, 201 Created
重定向 300-399 需要进一步操作 301 Moved, 302 Found
客户端错误 400-499 请求有误 400 Bad Request, 404 Not Found
服务器错误 500-599 服务器处理失败 500 Internal Error, 503 Unavailable

HTTPS vs HTTP

HTTPS (HTTP Secure) 是HTTP的安全版本,使用TLS/SSL加密通信:

特性 HTTP HTTPS
端口 80 443
加密 TLS/SSL加密
证书 不需要 需要SSL证书
安全性 明文传输,易被窃听 加密传输,安全可靠
性能 较快 略慢(加密开销)
应用场景 公开信息 敏感数据、登录、支付

RESTful API

REST (Representational State Transfer) 是一种Web服务架构风格:

核心原则: - 资源导向:一切皆资源,使用URL标识 - 统一接口:使用标准HTTP方法操作资源 - 无状态:每个请求包含完整信息 - 可缓存:响应可被缓存提高性能

URL设计示例

GET    /api/sensors          # 获取所有传感器列表
GET    /api/sensors/1        # 获取ID为1的传感器
POST   /api/sensors          # 创建新传感器
PUT    /api/sensors/1        # 更新ID为1的传感器
DELETE /api/sensors/1        # 删除ID为1的传感器

步骤1:ESP32 HTTP客户端基础

1.1 创建Arduino项目

创建新的Arduino项目:ESP32_HTTP_Client

1.2 基础配置代码

#include <WiFi.h>
#include <HTTPClient.h>

// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";

// API端点
const char* serverName = "http://jsonplaceholder.typicode.com/posts/1";

void setup() {
    // 初始化串口
    Serial.begin(115200);
    delay(1000);

    // 连接WiFi
    Serial.println("连接WiFi...");
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("\nWiFi连接成功");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());
}

void loop() {
    // 主循环代码将在后续添加
    delay(10000);  // 每10秒执行一次
}

配置说明: - ssidpassword:替换为你的WiFi信息 - serverName:API端点URL - JSONPlaceholder是免费的REST API测试服务

1.3 实现GET请求

void performGETRequest() {
    // 检查WiFi连接
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;

        // 指定请求目标
        http.begin(serverName);

        // 发送GET请求
        int httpResponseCode = http.GET();

        // 检查响应
        if (httpResponseCode > 0) {
            Serial.print("HTTP响应码: ");
            Serial.println(httpResponseCode);

            // 获取响应内容
            String payload = http.getString();
            Serial.println("响应内容:");
            Serial.println(payload);
        } else {
            Serial.print("错误码: ");
            Serial.println(httpResponseCode);
        }

        // 释放资源
        http.end();
    } else {
        Serial.println("WiFi未连接");
    }
}

void loop() {
    performGETRequest();
    delay(10000);  // 每10秒请求一次
}

代码说明: - http.begin():初始化HTTP连接 - http.GET():发送GET请求,返回HTTP状态码 - http.getString():获取响应体内容 - http.end():关闭连接,释放资源

预期输出

HTTP响应码: 200
响应内容:
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere...",
  "body": "quia et suscipit..."
}

步骤2:处理JSON响应

2.1 安装ArduinoJson库

在Arduino IDE中: - 打开 工具 -> 管理库 - 搜索 "ArduinoJson" - 安装最新版本(v6.x)

2.2 解析JSON数据

#include <ArduinoJson.h>

void performGETWithJSON() {
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin(serverName);

        int httpResponseCode = http.GET();

        if (httpResponseCode == 200) {
            String payload = http.getString();

            // 创建JSON文档(根据响应大小调整容量)
            DynamicJsonDocument doc(1024);

            // 解析JSON
            DeserializationError error = deserializeJson(doc, payload);

            if (error) {
                Serial.print("JSON解析失败: ");
                Serial.println(error.c_str());
                return;
            }

            // 提取数据
            int userId = doc["userId"];
            int id = doc["id"];
            const char* title = doc["title"];
            const char* body = doc["body"];

            // 打印解析结果
            Serial.println("=== 解析结果 ===");
            Serial.print("用户ID: ");
            Serial.println(userId);
            Serial.print("文章ID: ");
            Serial.println(id);
            Serial.print("标题: ");
            Serial.println(title);
            Serial.print("内容: ");
            Serial.println(body);
        }

        http.end();
    }
}

JSON解析说明: - DynamicJsonDocument:动态分配内存的JSON文档 - deserializeJson():解析JSON字符串 - 使用doc["key"]访问JSON字段 - 检查error确保解析成功

步骤3:发送POST请求

3.1 创建JSON数据并发送

void performPOSTRequest() {
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;

        // POST端点
        http.begin("http://jsonplaceholder.typicode.com/posts");

        // 设置请求头
        http.addHeader("Content-Type", "application/json");

        // 创建JSON数据
        DynamicJsonDocument doc(200);
        doc["title"] = "ESP32 Post";
        doc["body"] = "This is a test from ESP32";
        doc["userId"] = 1;

        // 序列化JSON
        String jsonString;
        serializeJson(doc, jsonString);

        Serial.println("发送POST请求:");
        Serial.println(jsonString);

        // 发送POST请求
        int httpResponseCode = http.POST(jsonString);

        if (httpResponseCode > 0) {
            Serial.print("HTTP响应码: ");
            Serial.println(httpResponseCode);

            String response = http.getString();
            Serial.println("响应:");
            Serial.println(response);
        } else {
            Serial.print("错误码: ");
            Serial.println(httpResponseCode);
        }

        http.end();
    }
}

POST请求说明: - http.addHeader():设置HTTP请求头 - Content-Type: application/json:指定发送JSON数据 - serializeJson():将JSON文档序列化为字符串 - http.POST():发送POST请求

预期响应

HTTP响应码: 201
响应:
{
  "title": "ESP32 Post",
  "body": "This is a test from ESP32",
  "userId": 1,
  "id": 101
}

步骤4:实现HTTPS安全连接

4.1 基础HTTPS请求

#include <WiFiClientSecure.h>

void performHTTPSRequest() {
    if (WiFi.status() == WL_CONNECTED) {
        WiFiClientSecure client;
        HTTPClient https;

        // 跳过证书验证(仅用于测试)
        client.setInsecure();

        // HTTPS端点
        if (https.begin(client, "https://jsonplaceholder.typicode.com/posts/1")) {
            int httpCode = https.GET();

            if (httpCode > 0) {
                Serial.printf("HTTPS响应码: %d\n", httpCode);

                if (httpCode == HTTP_CODE_OK) {
                    String payload = https.getString();
                    Serial.println(payload);
                }
            } else {
                Serial.printf("HTTPS请求失败: %s\n", https.errorToString(httpCode).c_str());
            }

            https.end();
        }
    }
}

HTTPS说明: - WiFiClientSecure:支持SSL/TLS的WiFi客户端 - client.setInsecure():跳过证书验证(不推荐用于生产环境) - HTTPS使用443端口

4.2 使用证书验证(推荐)

// 根证书(示例:Let's Encrypt)
const char* root_ca = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \
"...\n" \
"-----END CERTIFICATE-----\n";

void performSecureHTTPSRequest() {
    if (WiFi.status() == WL_CONNECTED) {
        WiFiClientSecure client;
        HTTPClient https;

        // 设置根证书
        client.setCACert(root_ca);

        if (https.begin(client, "https://api.example.com/data")) {
            int httpCode = https.GET();

            if (httpCode > 0) {
                Serial.printf("响应码: %d\n", httpCode);
                String payload = https.getString();
                Serial.println(payload);
            }

            https.end();
        }
    }
}

证书验证说明: - client.setCACert():设置CA根证书 - 证书可从服务器获取或使用系统根证书 - 验证证书确保连接安全性

步骤5:实现传感器数据上报

5.1 集成DHT11传感器

#include <DHT.h>

#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// API端点(替换为你的服务器)
const char* apiEndpoint = "http://your-server.com/api/sensor-data";

void setup() {
    Serial.begin(115200);
    dht.begin();

    // WiFi连接代码...
}

void uploadSensorData() {
    // 读取传感器数据
    float temperature = dht.readTemperature();
    float humidity = dht.readHumidity();

    if (isnan(temperature) || isnan(humidity)) {
        Serial.println("读取传感器失败");
        return;
    }

    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin(apiEndpoint);
        http.addHeader("Content-Type", "application/json");

        // 构建JSON数据
        DynamicJsonDocument doc(256);
        doc["device_id"] = "ESP32_001";
        doc["temperature"] = temperature;
        doc["humidity"] = humidity;
        doc["timestamp"] = millis();

        String jsonData;
        serializeJson(doc, jsonData);

        Serial.println("上传数据:");
        Serial.println(jsonData);

        // 发送POST请求
        int httpCode = http.POST(jsonData);

        if (httpCode > 0) {
            Serial.printf("响应码: %d\n", httpCode);
            String response = http.getString();
            Serial.println(response);
        } else {
            Serial.printf("上传失败: %s\n", http.errorToString(httpCode).c_str());
        }

        http.end();
    }
}

void loop() {
    uploadSensorData();
    delay(60000);  // 每分钟上传一次
}

5.2 添加错误重试机制

bool uploadWithRetry(int maxRetries = 3) {
    for (int i = 0; i < maxRetries; i++) {
        if (WiFi.status() != WL_CONNECTED) {
            Serial.println("WiFi断开,重新连接...");
            WiFi.reconnect();
            delay(5000);
            continue;
        }

        HTTPClient http;
        http.begin(apiEndpoint);
        http.addHeader("Content-Type", "application/json");
        http.setTimeout(10000);  // 10秒超时

        // 构建数据...
        String jsonData = buildSensorJSON();

        int httpCode = http.POST(jsonData);
        http.end();

        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
            Serial.println("上传成功");
            return true;
        }

        Serial.printf("尝试 %d/%d 失败,响应码: %d\n", i+1, maxRetries, httpCode);
        delay(2000);  // 等待2秒后重试
    }

    Serial.println("上传失败,已达最大重试次数");
    return false;
}

步骤6:创建简单的Web服务器

6.1 基础Web服务器

#include <WebServer.h>

WebServer server(80);  // 创建Web服务器,端口80

// LED控制引脚
const int LED_PIN = 2;

void handleRoot() {
    String html = "<html><body>";
    html += "<h1>ESP32 Web Server</h1>";
    html += "<p>LED Status: ";
    html += digitalRead(LED_PIN) ? "ON" : "OFF";
    html += "</p>";
    html += "<p><a href='/led/on'><button>Turn ON</button></a></p>";
    html += "<p><a href='/led/off'><button>Turn OFF</button></a></p>";
    html += "</body></html>";

    server.send(200, "text/html", html);
}

void handleLEDOn() {
    digitalWrite(LED_PIN, HIGH);
    server.send(200, "text/plain", "LED is ON");
}

void handleLEDOff() {
    digitalWrite(LED_PIN, LOW);
    server.send(200, "text/plain", "LED is OFF");
}

void handleNotFound() {
    server.send(404, "text/plain", "404: Not Found");
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);

    // WiFi连接...

    // 配置路由
    server.on("/", handleRoot);
    server.on("/led/on", handleLEDOn);
    server.on("/led/off", handleLEDOff);
    server.onNotFound(handleNotFound);

    // 启动服务器
    server.begin();
    Serial.println("HTTP服务器已启动");
    Serial.print("访问: http://");
    Serial.println(WiFi.localIP());
}

void loop() {
    server.handleClient();  // 处理客户端请求
}

Web服务器说明: - WebServer:ESP32内置的Web服务器类 - server.on():注册路由处理函数 - server.send():发送HTTP响应 - server.handleClient():处理客户端请求

6.2 RESTful API服务器

void handleGetSensorData() {
    float temp = dht.readTemperature();
    float hum = dht.readHumidity();

    DynamicJsonDocument doc(200);
    doc["temperature"] = temp;
    doc["humidity"] = hum;
    doc["timestamp"] = millis();

    String response;
    serializeJson(doc, response);

    server.send(200, "application/json", response);
}

void handlePostControl() {
    if (server.hasArg("plain")) {
        String body = server.arg("plain");

        DynamicJsonDocument doc(200);
        DeserializationError error = deserializeJson(doc, body);

        if (error) {
            server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
            return;
        }

        const char* action = doc["action"];

        if (strcmp(action, "led_on") == 0) {
            digitalWrite(LED_PIN, HIGH);
            server.send(200, "application/json", "{\"status\":\"success\",\"led\":\"on\"}");
        } else if (strcmp(action, "led_off") == 0) {
            digitalWrite(LED_PIN, LOW);
            server.send(200, "application/json", "{\"status\":\"success\",\"led\":\"off\"}");
        } else {
            server.send(400, "application/json", "{\"error\":\"Unknown action\"}");
        }
    } else {
        server.send(400, "application/json", "{\"error\":\"No body\"}");
    }
}

void setup() {
    // ... 其他初始化 ...

    // RESTful API路由
    server.on("/api/sensor", HTTP_GET, handleGetSensorData);
    server.on("/api/control", HTTP_POST, handlePostControl);

    server.begin();
}

API测试

使用curl测试API:

# 获取传感器数据
curl http://192.168.1.100/api/sensor

# 控制LED
curl -X POST http://192.168.1.100/api/control \
  -H "Content-Type: application/json" \
  -d '{"action":"led_on"}'

步骤7:添加认证和安全性

7.1 基础认证(Basic Auth)

const char* www_username = "admin";
const char* www_password = "password123";

void handleSecureAPI() {
    // 检查认证
    if (!server.authenticate(www_username, www_password)) {
        server.requestAuthentication();
        return;
    }

    // 认证通过,处理请求
    server.send(200, "application/json", "{\"message\":\"Authenticated\"}");
}

void setup() {
    // ...
    server.on("/api/secure", HTTP_GET, handleSecureAPI);
    // ...
}

7.2 API密钥认证

const char* API_KEY = "your-secret-api-key-here";

bool validateAPIKey() {
    if (server.hasHeader("X-API-Key")) {
        String apiKey = server.header("X-API-Key");
        return apiKey == API_KEY;
    }
    return false;
}

void handleProtectedAPI() {
    if (!validateAPIKey()) {
        server.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
        return;
    }

    // API密钥验证通过
    server.send(200, "application/json", "{\"message\":\"Access granted\"}");
}

使用API密钥

curl http://192.168.1.100/api/protected \
  -H "X-API-Key: your-secret-api-key-here"

故障排除

问题1:HTTP请求超时

可能原因: - 网络连接不稳定 - 服务器响应慢 - DNS解析失败 - 防火墙阻止

解决方法

// 设置超时时间
http.setTimeout(15000);  // 15秒

// 检查DNS解析
IPAddress serverIP;
if (WiFi.hostByName("api.example.com", serverIP)) {
    Serial.print("服务器IP: ");
    Serial.println(serverIP);
}

问题2:JSON解析失败

可能原因: - JSON格式错误 - 内存不足 - 响应被截断

解决方法

// 增加JSON文档容量
DynamicJsonDocument doc(2048);  // 增加到2KB

// 检查解析错误
DeserializationError error = deserializeJson(doc, payload);
if (error) {
    Serial.print("解析失败: ");
    Serial.println(error.c_str());
    Serial.println("原始数据:");
    Serial.println(payload);
}

问题3:HTTPS证书验证失败

可能原因: - 证书过期 - 证书不匹配 - 时间不同步 - 根证书错误

解决方法

// 1. 同步时间(HTTPS需要正确的时间)
configTime(0, 0, "pool.ntp.org", "time.nist.gov");

// 2. 使用正确的根证书
// 从浏览器导出或使用openssl获取

// 3. 临时跳过验证(仅测试)
client.setInsecure();

问题4:Web服务器无响应

可能原因: - 未调用server.handleClient() - 路由配置错误 - 端口被占用 - 防火墙阻止

解决方法

void loop() {
    server.handleClient();  // 必须在loop中调用
    delay(2);  // 给系统一些时间处理
}

// 检查服务器状态
Serial.print("服务器运行在: http://");
Serial.print(WiFi.localIP());
Serial.println(":80");

总结

通过本教程,你学习了:

  • ✅ HTTP协议的核心概念和请求/响应模型
  • ✅ HTTP方法(GET、POST)的实际应用
  • ✅ HTTPS加密通信和证书验证
  • ✅ ESP32 HTTP客户端的完整开发
  • ✅ JSON数据的解析和构建
  • ✅ RESTful API的调用和实现
  • ✅ 简单Web服务器的创建

关键要点: - HTTP是Web通信的基础协议 - HTTPS提供加密和安全保障 - RESTful API是现代Web服务的标准 - ESP32可以同时作为HTTP客户端和服务器 - JSON是数据交换的首选格式

进阶挑战

尝试以下挑战来巩固学习:

  1. 挑战1:实现完整的CRUD操作(创建、读取、更新、删除)
  2. 挑战2:添加OTA固件更新功能(通过HTTP下载)
  3. 挑战3:实现文件上传功能(multipart/form-data)
  4. 挑战4:创建响应式Web界面(HTML+CSS+JavaScript)
  5. 挑战5:集成第三方API(天气、时间、地图等)

完整代码

完整的项目代码可以在GitHub上找到:

GitHub仓库: https://github.com/embedded-knowledge/http-esp32-tutorial

项目包含: - HTTP客户端示例 - HTTPS安全连接示例 - Web服务器示例 - RESTful API实现 - 传感器数据上报示例

下一步

建议继续学习:

  • WebSocket实时通信 - 学习双向实时通信
  • CoAP轻量级协议 - 学习物联网专用协议
  • MQTT协议应用 - 学习发布订阅模式
  • OTA固件更新 - 学习远程升级技术

参考资料

  1. HTTP/1.1规范 - https://tools.ietf.org/html/rfc2616
  2. HTTPS和TLS - https://tools.ietf.org/html/rfc5246
  3. RESTful API设计指南 - https://restfulapi.net/
  4. ESP32 HTTPClient文档 - https://github.com/espressif/arduino-esp32/tree/master/libraries/HTTPClient
  5. ArduinoJson文档 - https://arduinojson.org/
  6. JSONPlaceholder测试API - https://jsonplaceholder.typicode.com/

常见HTTP术语

  • URL:统一资源定位符,网络资源的地址
  • URI:统一资源标识符,资源的唯一标识
  • Header:HTTP头部,包含元数据信息
  • Body:HTTP主体,包含实际数据
  • Status Code:状态码,表示请求处理结果
  • Content-Type:内容类型,指定数据格式
  • User-Agent:用户代理,标识客户端信息
  • Cookie:小型数据片段,用于状态管理

反馈:如果你在学习过程中遇到问题,欢迎在评论区留言或提交Issue!