為什麼要在 ESP32 上跑 Web Server?
ESP32 Web Server 是 IoT 專案的核心技術。ESP32 內建 WiFi,效能足以同時處理多個 HTTP 連線,讓它能扮演 IoT 裝置+網頁伺服器的雙重角色:
- 無需雲端:瀏覽器直接連 ESP32,不依賴第三方平台
- 即時控制:點一下網頁按鈕就切換 GPIO,延遲 < 100ms
- 儀表板:在手機/電腦上隨時查看感測器數據
- 檔案下載:從 LittleFS 下載 CSV 記錄檔
- REST API:提供 JSON 介面給其他程式或 MQTT 橋接

HTTP 通訊基本流程
Web Server 的核心是 HTTP 協定——基於 TCP 的請求和回應模型:
流程很簡單:
- TCP 三次握手:Client 發 SYN → Server 回 SYN-ACK → Client 回 ACK
- HTTP 請求:Client 發送 GET / HTTP/1.1 等請求
- HTTP 回應:Server 回傳 200 OK + HTML 內容
- 連線關閉:短連線模式下,傳完即斷
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 += "
";
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 更具靈活性——前端可自由決定如何渲染:
// 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);
// ...
}
路由設計

良好的路由設計讓 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,讓儀表板自動更新:
// 前端 HTML + JavaScript(放在 LittleFS 的 index.html 中)
完整 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 處理更複雜的場景。
文章評論