0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

ESP32 Web Server 網頁伺服器完整教學:從 HTML 到 REST API 實作

2026 年 6 月 10 日 24點熱度 0人點贊 0條評論

為什麼要在 ESP32 上跑 Web Server?

ESP32 Web Server 是 IoT 專案的核心技術。ESP32 內建 WiFi,效能足以同時處理多個 HTTP 連線,讓它能扮演 IoT 裝置+網頁伺服器的雙重角色:

  • 無需雲端:瀏覽器直接連 ESP32,不依賴第三方平台
  • 即時控制:點一下網頁按鈕就切換 GPIO,延遲 < 100ms
  • 儀表板:在手機/電腦上隨時查看感測器數據
  • 檔案下載:從 LittleFS 下載 CSV 記錄檔
  • REST API:提供 JSON 介面給其他程式或 MQTT 橋接

ESP32 Web Server 架構

HTTP 通訊基本流程

Web Server 的核心是 HTTP 協定——基於 TCP 的請求和回應模型:

HTTP 請求-回應流程

流程很簡單:

  1. TCP 三次握手:Client 發 SYN → Server 回 SYN-ACK → Client 回 ACK
  2. HTTP 請求:Client 發送 GET / HTTP/1.1 等請求
  3. HTTP 回應:Server 回傳 200 OK + HTML 內容
  4. 連線關閉:短連線模式下,傳完即斷

ESP32 Arduino:最簡單的 Web Server

// ESP32 Arduino - 最簡單的 Web Server
#include 
#include 

const char* ssid = "YourSSID";
const char* password = "YourPassword";

WebServer server(80);  // 監聽埠 80

// 首頁路由
void handleRoot() {
    String html = "";
    html += "";
    html += "";
    html += "";
    html += "

ESP32 Web Server

";
html += "

GPIO 2: ON | OFF

";
html += "

目前燈狀態: " + String(digitalRead(2) ? "ON" : "OFF") + "

";
html += "

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

void handleOn() {
    digitalWrite(2, HIGH);
    server.sendHeader("Location", "/");
    server.send(302);  // 重新導回首頁
}

void handleOff() {
    digitalWrite(2, LOW);
    server.sendHeader("Location", "/");
    server.send(302);
}

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

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

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500); Serial.print(".");
    }
    Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str());

    // 註冊路由
    server.on("/",      handleRoot);
    server.on("/on",    handleOn);
    server.on("/off",   handleOff);
    server.onNotFound(handleNotFound);

    server.begin();
    Serial.println("HTTP Server 已啟動");
}

void loop() {
    server.handleClient();  // 處理 HTTP 請求
}

REST API + JSON 回應

比起回傳 HTML,REST API 回傳 JSON 更具靈活性——前端可自由決定如何渲染:

REST API 請求和回應

// ESP32 REST API - JSON 格式回應
#include 

// 讀取溫度 API
void handleGetTemp() {
    float temp = readTemperature();  // 自訂感測器讀取函式

    JsonDocument doc;
    doc["temperature"] = temp;
    doc["unit"] = "celsius";
    doc["timestamp"] = millis() / 1000;

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

// 控制 LED API(接受 JSON Body)
void handlePostLed() {
    if (!server.hasArg("plain")) {
        server.send(400, "application/json", "{\"error\":\"no body\"}");
        return;
    }

    JsonDocument doc;
    DeserializationError err = deserializeJson(doc, server.arg("plain"));

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

    bool state = doc["state"] | false;
    digitalWrite(2, state ? HIGH : LOW);

    JsonDocument resp;
    resp["status"] = "ok";
    resp["led"] = state;

    String out;
    serializeJson(resp, out);
    server.send(200, "application/json", out);
}

void setup() {
    // ...
    server.on("/api/temperature", HTTP_GET,  handleGetTemp);
    server.on("/api/led",         HTTP_POST, handlePostLed);
    // ...
}

路由設計

ESP32 Web Server 路由設計

良好的路由設計讓 Web Server 容易維護。建議依功能分類:

  • / — HTML 頁面(使用者介面)
  • /api/* — REST API(JSON 資料)
  • /download — 檔案下載
  • /update — OTA 韌體更新

從 LittleFS 提供靜態檔案

儲存 HTML/CSS/JS 在 LittleFS 中,比在程式碼中用字串拼接 HTML 優雅得多:

// 從 LittleFS 提供靜態檔案
#include 

void setup() {
    LittleFS.begin(true);

    // 提供 index.html(從 LittleFS 讀取)
    server.on("/", []() {
        File f = LittleFS.open("/index.html", FILE_READ);
        if (!f) {
            server.send(500, "text/plain", "Internal Error");
            return;
        }
        server.streamFile(f, "text/html");
        f.close();
    });

    // 提供靜態資源(CSS, JS, 圖片)
    server.on("/style.css", []() {
        File f = LittleFS.open("/style.css", FILE_READ);
        if (f) {
            server.streamFile(f, "text/css");
            f.close();
        }
    });

    server.serveStatic("/assets", LittleFS, "/assets");
}

AJAX 自動更新(無需重新整理頁面)

用 AJAX 定時輪詢 API,讓儀表板自動更新:

AJAX Polling 輪詢

// 前端 HTML + JavaScript(放在 LittleFS 的 index.html 中)

完整 IoT 儀表板範例

結合以上所有技術,做出即時監控儀表板:

ESP32 IoT 儀表板

// ESP32 完整 IoT 儀表板(合併範例)
#include 
#include 
#include 
#include 

WebServer server(80);

// 模擬感測器資料
float readTemp() { return 25.0 + sin(millis()/10000.0) * 3; }
float readHum()  { return 60.0 + cos(millis()/12000.0) * 5; }

void handleRoot() {
    File f = LittleFS.open("/dashboard.html", FILE_READ);
    if (f) { server.streamFile(f, "text/html"); f.close(); }
    else   { server.send(200, "text/plain", "dashboard.html not found"); }
}

void handleApiTemp() {
    JsonDocument doc;
    doc["temperature"] = readTemp();
    doc["humidity"]    = readHum();
    doc["uptime"]      = millis() / 1000;
    String out; serializeJson(doc, out);
    server.send(200, "application/json", out);
}

void handleApiLed() {
    if (server.method() == HTTP_POST) {
        JsonDocument doc;
        deserializeJson(doc, server.arg("plain"));
        bool state = doc["state"] | false;
        digitalWrite(2, state ? HIGH : LOW);

        JsonDocument resp;
        resp["status"] = "ok";
        resp["led"] = state;
        String out; serializeJson(resp, out);
        server.send(200, "application/json", out);
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(2, OUTPUT);
    LittleFS.begin(true);

    WiFi.begin("SSID", "PASSWORD");
    while (WiFi.status() != WL_CONNECTED) delay(500);

    server.on("/",            handleRoot);
    server.on("/api/data",    handleApiTemp);
    server.on("/api/led",     handleApiLed);
    server.serveStatic("/assets", LittleFS, "/assets");

    server.begin();
    Serial.printf("ESP32 Web: http://%s\n", WiFi.localIP().toString().c_str());
}

void loop() {
    server.handleClient();
}

安全性注意事項

問題 風險 解決方案
開放 80 埠 區域網路內任何人都可連線 加上密碼驗證(HTTP Basic Auth)
明文傳輸 WiFi 封包可被監聽 啟用 HTTPS(需 SSL 憑證,ESP32 可用)
CORS 外部網站可呼叫 API 檢查 Origin Header 或關閉 CORS
輸入驗證 惡意 JSON 可能造成崩潰 使用 ArduinoJson 的 DeserializationError
DoS 大量請求佔用 CPU 限制連線數、加上 Rate Limiting
// 簡易 HTTP Basic Auth
void handleRoot() {
    if (!server.authenticate("admin", "password123")) {
        server.requestAuthentication();
        return;
    }
    // 驗證通過,提供網頁
    server.send(200, "text/html", "<h1>秘密頁面</h1>");
}

AsyncWebServer(高效能替代方案)

WebServer.h 是同步(阻塞)模式,不適合處理大量請求。AsyncWebServer 是非同步版本,效能好很多:

// ESPAsyncWebServer 範例
#include 

AsyncWebServer server(80);

void setup() {
    // AsyncWebServer 不需在 loop() 中呼叫 handleClient()
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send(200, "text/plain", "Hello from AsyncWebServer!");
    });

    // JSON API
    server.on("/api/temp", HTTP_GET, [](AsyncWebServerRequest *request) {
        String json = "{\"temp\":25.3}";
        request->send(200, "application/json", json);
    });

    // LittleFS static files
    server.serveStatic("/", LittleFS, "/");

    server.begin();
}

void loop() {
    // AsyncWebServer 不需要 handleClient()!
    // CPU 可以專心做其他事,如讀感測器
}

Comparing WebServer Libraries

特性 WebServer.h AsyncWebServer
運作方式 同步(loop 輪詢) 非同步(中斷驅動)
同時連線 有限(~4 個) 大量(~20+ 個)
主迴圈影響 需不斷呼叫 handleClient() 背景執行,不阻塞
WebSocket 不支援 內建支援
檔案上傳 有限 完整支援
使用難度 簡單 中等(回呼較多)

建議:簡單專案用 WebServer.h,高效能/多連線用 AsyncWebServer。

ESP32 同時跑 Web Server + MQTT

Web Server 常與 MQTT 搭配:Web Server 提供區域網路控制介面,MQTT 將資料上傳到雲端:

// Web Server + MQTT 雙工
// 區域網路: 瀏覽器 → Web Server → ESP32 GPIO
// 廣域網路: ESP32 → MQTT Broker → 手機 App

void onWebApiLed(AsyncWebServerRequest *request) {
    // 控制本地 GPIO
    digitalWrite(2, HIGH);

    // 同時透過 MQTT 通知雲端
    client.publish("esp32/led", "on");

    request->send(200, "application/json", "{\"status\":\"ok\"}");
}

總結

ESP32 的 Web Server 能力讓它不僅是感測器節點,更是一個完整的 IoT 閘道器。從最簡單的 HTML 控制頁面到 REST API + AJAX 即時儀表板,甚至 AsyncWebServer 的高效能應用,ESP32 都能勝任。

建議入門先從 WebServer.h 開始,熟悉路由設計和 API 模式後,再升級到 AsyncWebServer 處理更複雜的場景。

標籤: 教學
最後更新:2026 年 6 月 10 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang