對於WebServer來說靜態文件也是非常重要的一塊。通常一個網頁有很多文件組成,比如一個主頁通常由index.html、favicon.ico等多個文件組成,用戶訪問 /index.html 時,瀏覽器接收到 index.html 文件後還會再請求該文件中關聯的其它文件,這些文件名稱和類型等都是無法預料的,一條條添加對應的url就非常不方便了,這個時候就需要用到靜態文件服務了。
靜態文件中通常可以使用模板引擎方便的實現界麵與數據的分離,減少開發過程中的工作量,特定情景下還是蠻好用的。
這篇文章將對上麵兩塊相關內容進行介紹。
本文中各例程演示均在ESP32中進行。
靜態文件服務
功能說明
使用靜態文件服務首先需要啟動相關的文件係統,除了可以使用Flash上的SPIFFS係統,還可以使用SD卡等。初始完成後就可以用 AsyncWebServer 對象的 serveStatic() 方法來設置靜態文件服務了,比如下麵這樣:
// 用戶訪問/page.htm時,返回SPIFFS中/www/page.htm文件
server.serveStatic("/page.htm", SPIFFS, "/www/page.htm");
1
2
上麵的方式指定了文件到文件,事實上大多數情況是不會這麼用的,而是下麵這樣:
// 用戶訪問/目錄下文件時返回SPIFFS中/www/路徑下同名文件
// 比如用戶訪問/page.htm時,將會返回SPIFFS中/www/page.htm文件
// 比如用戶訪問/favicon.ico時,將會返回SPIFFS中/www/favicon.ico文件
// ……
// 特殊情況:用戶訪問/時,默認將會返回SPIFFS中/www/index.htm文件(如果存在)
server.serveStatic("/", SPIFFS, "/www/");
1
2
3
4
5
6
上麵上麵的使用方式是最常用的方式,上麵的方式中有一種特殊情況,就是用戶訪問根目錄時返回了index.htm文件,這個默認返回的文件也可以手動定義,使用下麵方法:
// 用戶訪問/時,默認返回將變成SPIFFS中/www/default.html文件
server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html");
1
2
客戶端如果多次打開網頁,每次都從服務器獲取靜態文件的話對性能影響還是蠻大的,這時候就可以使用Cache-Control和Last-Modified來優化性能。
Cache-Control可以設定一個時間,客戶端在獲取到某個文件後將文件進行緩存,這段時間內如果再需要這個文件的話將不再從服務器獲取,而是直接使用緩存。下麵是Cache-Control的使用示例:
// 設定緩存事件600秒,客戶端獲取過的文件將在客戶端緩存600秒
// 600秒內需要已緩存的文件將直接使用緩存,不再從服務器獲取
server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600");
// 使用下麵方式就可以隨時更改Cache-Control
AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600");
handler->setCacheControl("max-age=30");
1
2
3
4
5
6
7
Cache-Control除了上麵功能外還有很多別的功能,不同功能下setCacheControl中填入的參數不同,比如填入 "no-store" 參數的話將禁用所有緩存。Cache-Control參數更多內容可以參考 Cache-Control 。
Last-Modified將設置一個時間參數,客戶端如果緩存過文件,下次再需要該文件向服務器請求時將附帶緩存文件的時間參數,服務器比較兩個參數,如果服務器中的參數較新的話將發送新的文件,否則客戶端將使用緩存文件。下麵是Last-Modified的使用示例:
// 設定文件最後修改時間參數
AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 26 Apr 2010 13:22:17 GMT");
Mon, 26 Apr 2010 13:22:17 GMT
// 更新文件最後修改時間參數
handler->setLastModified("Mon, 20 Jun 2016 14:00:00 GMT");
1
2
3
4
5
6
靜態文件服務可以設置身份認證,用戶必須通過認證後才能訪問其中的文件:
// setAuthentication()中分別填入用戶名和密碼
server.serveStatic("/", SPIFFS, "/www/").setAuthentication("user", "pass");
1
2
除了上麵的功能外靜態文件服務還可以應用過濾器和模板引擎。下麵是應用過濾器的示例(模板引擎的示例將在模板引擎章節介紹):
server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_STA_FILTER); // ESP32處於STA模式時起效
server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER); // ESP32處於AP模式時起效
1
2
從上麵的介紹可以看到靜態文件服務可選的參數非常多,如果需要設置多個參數的話可以使用下麵兩種方式:
// 方式一
server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html").setCacheControl("max-age=600");
// 方式二
AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/");
handler->setDefaultFile("default.html");
handler->setCacheControl("max-age=600");
1
2
3
4
5
6
7
使用演示
這裏以使用SPIFFS作為文件係統進行靜態文件服務功能演示,首先準備幾個文件。將下麵文本保存為名為index.html的文件:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>ServeStatic Test</title>
<link rel="stylesheet" type="text/css" href="mystyle.css">
</head>
<body>
<p>這是靜態文件服務測試</p>
<p>本頁麵的背景顏色是由mystyle.css文件提供的</p>
<p>本頁麵引用了mystyle.css文件,瀏覽器在解析時會自動向服務器請求該文件</p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
將下麵文本保存為名為mystyle.css的文件:
body {
background-color: gray;
}
1
2
3
在Arduino中使用下麵代碼:
#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
AsyncWebServer server(80); //聲明WebServer對象
void setup()
{
Serial.begin(115200);
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("Connected");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
SPIFFS.begin(true); //掛載SPIFFS,如果掛載失敗則格式化生成SPIFFS,格式化時間較長
//當然上麵begin中不填true格式化也行,下麵演示中用工具上傳文件時會自動生成SPIFFS
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop(){}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
上麵使用了名為 ESP32 Sketch Data Upload 的工具來將 項目文件夾下data文件夾中 的網頁相關文件上傳到ESP32的SPIFFS中。一切就緒後在瀏覽器中訪問就可以看到網頁被正確的加載出來了,單獨訪問兩個文件也沒問題。
上傳文件用到的工具項目地址如下:
https://github.com/me-no-dev/arduino-esp32fs-plugin
從項目地址下載Release版本壓縮包,將其解壓到Arduino安裝文件夾tools文件夾下,最終文件路徑為: .../Arduino/tools/ESP32FS/tool/esp32fs.jar 。重啟Arduino IDE後就可以在菜單中看到選項。
ESP8266上傳文件到SPIFFS的工具項目地址如下:
https://github.com/esp8266/arduino-esp8266fs-plugin
SPIFFSEditor
ESPAsyncWebServer庫中有一個SPIFFSEditor功能,可以通過網頁管理和編輯文件係統中的文件,這裏做個簡單的使用演示:
#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h> //引入相應庫
#include <SPIFFSEditor.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
AsyncWebServer server(80); //聲明WebServer對象
const char* http_username = "admin";
const char* http_password = "123456";
void setup()
{
Serial.begin(115200);
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("Connected");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
SPIFFS.begin(true);
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop(){}
上麵就是SPIFFSEditor的演示了,需要注意的是SPIFFSEditor的網頁中用到了外部的文件,需要能連上互聯網才能正確加載。
模板引擎
ESPAsyncWebServer中的模板引擎目前隻支持一個功能,實際值替換占位符。該模板引擎使用 % 來標識占位符,先看下麵演示:
#include <WiFi.h>
#include <ESPAsyncWebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
AsyncWebServer server(80); //聲明WebServer對象
int count = 0;
String processor(const String& var)
{
if(var == "VAR1")
{
count++;
return String(count);
}
else if(var == "VAR2")
{
return "lalala~~~";
}
else if(var == "VAR3")
{
return F("ABCDEFG"); // F() 是Arduino的PROGMEM機製,裏麵的字符串將存放在Flash中,減少對內存的占用
}
return String();
}
void handleRoot(AsyncWebServerRequest *request) //回調函數
{
// 向客戶端發送響應和內容
// 注意文本中用 % % 包圍的字符串,
request->send_P(200, "text/plain", "count = %VAR1%, VAR2 = %VAR2%, VAR3 = %VAR3%", processor);
}
void setup()
{
Serial.begin(115200);
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("Connected");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, handleRoot); //注冊鏈接"/"與對應回調函數
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop(){}
ESPAsyncWebServer的模板引擎因為功能單一,從上麵的演示中就可以完全理解了,模板引擎經常用在靜態的文件中。除了上麵演示用的方式還可以用下麵的方式來使用:
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html);
request->send(response);
1
2
3
或者直接在靜態文件服務上使用也可以:
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor);