BLOG8
跳转至

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秒执行一次
}

配置说明: - ssid 和 password:替换为你的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!