轉至 https://blog.csdn.net/Naisu_kun/article/details/88572129
11目的
WebServer是非常常用的一個功能,在設備上使用該功能用戶就可以不依賴app直接通過瀏覽器訪問和操作設備。另外即使是用app的,對於app開發來說直接訪問webapi也比處理tcp/udp要方便些。
使用詳解
基本使用
WebServer簡單點理解就是網頁服務器,主要干的活就是用戶訪問鏈接的時候執行相應的動作,對於開發來說主要處理的就是注冊鏈接並編寫用戶訪問該鏈接時需要執行的操作。
使用步驟如下:
引入相應庫#include <WebServer.h>;
聲明WebServer對象並設置端口號,一般WebServer端口號使用80;
使用on()方法注冊鏈接與回調函數;
使用begin()方法啟動服務器進行請求監聽;
使用handleClient()處理來自客戶端的請求;
可以使用下面代碼進行測試:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
void handleRoot() //回調函數
{
server.send(200, "text/plain", "這是根頁面");
}
void handleP1() //回調函數
{
server.send(200, "text/plain", "這是P1頁面");
}
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("/", handleRoot); //注冊鏈接"/"與對應回調函數
server.on("/p1", handleP1); //注冊鏈接"/p1"與對應回調函數
server.on("/p2", []() { //注冊鏈接"/p2",對應回調函數通過內聯函數聲明
server.send(200, "text/plain", "這是P2頁面,由內聯函數聲明");
});
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
使用路徑參數
如果你有很多相似的鏈接比如/user/1、/user/2、/user/3、/user/4……,使用上面的方法時就需要每個鏈接都需要進行聲明注冊,比較不方便,這里可以使用路徑參數來處理這些相似的或是動態的鏈接,可以用下面的代碼進行測試:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
void handleArg1() //回調函數
{
String arg = server.pathArg(0);
server.send(200, "text/plain", "這是鏈接/{},參數是: " + arg);
}
void handleArg2() //回調函數
{
String arg0 = server.pathArg(0);
String arg1 = server.pathArg(1);
server.send(200, "text/plain", "這是鏈接/p/{}/d/{},參數是: " + arg0 + " 、 " + arg1);
}
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("/{}", handleArg1); //注冊鏈接與回調函數
server.on("/p/{}/d/{}", handleArg2); //注冊鏈接與回調函數
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
41
42
43
44
45
46
47
48
49
設置未注冊頁面響應
如果用戶訪問了未注冊的的鏈接時我們最好能給個提示,比如我們在上網時經常能見到的“網頁不存在”、“404 Not Found”等。在這里我們可以用onNotFound()方法來給出這樣的提示,用戶在訪問不存在的鏈接時會跳轉到該方法所綁定的回調函數上,可以用下面代碼進行測試:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
void handleNotFound() //未注冊鏈接回調函數
{
server.send(404, "text/plain", "訪問的頁面不存在哦");
}
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.onNotFound(handleNotFound); //未注冊鏈接回調函數注冊
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
用戶認證
用戶認證可以提供一定的安全性,這里提供了BASIC_AUTH和DIGEST_AUTH兩種方式,一般來說DIGEST_AUTH方式安全性稍高些,下面代碼進行了基本的測試:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
const char *username = "user"; //用戶名
const char *userpassword = "1234"; //用戶密碼
void handleRoot() //回調函數
{
if (!server.authenticate(username, userpassword)) //校驗用戶是否登錄
{
return server.requestAuthentication(); //請求進行用戶登錄認證
}
server.send(200, "text/plain", "登錄成功!"); //登錄成功則顯示真正的內容
}
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("/", handleRoot); //注冊鏈接和回調函數
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
41
42
43
44
45
46
47
請求方信息
客戶端請求鏈接,我們也能夠知道客戶端請求的一些信息,可以用下面代碼進行測試:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
void handleRoot() //回調函數
{
String message = "客戶端信息:";
message += "\nClient IP: ";
IPAddress addr = server.client().remoteIP(); //客戶端ip
message += String(addr[0]) + "." + String(addr[1]) + "." + String(addr[2]) + "." + String(addr[3]);
message += "\nURI: ";
message += server.uri(); //打印當前url
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST"; //判斷http請求方法
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++)
{
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(200, "text/plain", message);
}
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("/msg", HTTP_GET, handleRoot); //注冊鏈接和回調函數
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
網頁與后台數據交互
網頁與后台數據交互需要用到網頁和html和ajax知識,可以參考:《從零開始的ESP8266探索(06)-使用Server功能搭建Web Server》中《通過網頁收發數據》章節
我們首先准備一個帶ajax腳本的網頁:
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 WebServer Test</title>
<script>
function getData() {
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//將獲取到的內容寫到txtRandomData中
document.getElementById("txtRandomData").innerHTML = xmlhttp.responseText;
}
},
xmlhttp.open("GET", "getRandomData", true); //以GET方法打開getRandomData
xmlhttp.send();
}
</script>
</head>
<body>
<div id="txtRandomData">Unkwon</div>
<input type="button" value="random" onclick="getData()">
</body>
</html>
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
然后通過工具將它轉換成字符串嵌入到代碼中,工具可以參考:http://tools.jb51.net/transcoding/html2js
也可以先壓縮網頁然后再轉換成字符串,這樣可以減小網頁大小,提高效率,可以參考:https://tool.lu/html/
下面就是帶有 網頁與后台數據交互功能 的完整代碼:
#include <WiFi.h>
#include <WebServer.h> //引入相應庫
//網頁
String myhtmlPage =
String("") +
"<html>" +
"<head>" +
" <title>ESP32 WebServer Test</title>" +
" <script>" +
" function getData() {" +
" var xmlhttp;" +
" if (window.XMLHttpRequest) {" +
" xmlhttp = new XMLHttpRequest();" +
" }" +
" else {" +
" xmlhttp = new ActiveXObject(\"Microsoft.XMLHTTP\");" +
" }" +
" xmlhttp.onreadystatechange = function () {" +
" if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {" +
" document.getElementById(\"txtRandomData\").innerHTML = xmlhttp.responseText;" +
" }" +
" }," +
" xmlhttp.open(\"GET\", \"getRandomData\", true); " +
" xmlhttp.send();" +
" }" +
" </script>" +
"</head>" +
"<body>" +
" <div id=\"txtRandomData\">Unkwon</div>" +
" <input type=\"button\" value=\"random\" οnclick=\"getData()\">" +
"</body>" +
"</html>";
const char *ssid = "********";
const char *password = "********";
WebServer server(80); //聲明WebServer對象
void handleRoot() //回調函數
{
server.send(200, "text/html", myhtmlPage); //!!!注意返回網頁需要用"text/html" !!!
}
void handleAjax() //回調函數
{
String message = "隨機數據:";
message += String(random(10000)); //取得隨機數
server.send(200, "text/plain", message); //將消息發送回頁面
}
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("/", handleRoot); //注冊鏈接和回調函數
server.on("/getRandomData", HTTP_GET, handleAjax); //注冊網頁中ajax發送的get方法的請求和回調函數
server.begin(); //啟動服務器
Serial.println("Web server started");
}
void loop()
{
server.handleClient(); //處理來自客戶端的請求
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
上面的代碼直接拷貝可能網頁的字符串部分代碼會出現編碼問題導致無法正確運行,該部分最好刪除重新手打。如果想偷懶的話可以試試只刪除 onclick 這個詞的 o 然后手打輸入。
可以看到當點擊網頁上random按鈕時觸發了getData()方法,該方法向服務器請求getRandomData鏈接,服務器在收到該請求后進行了響應,把數據返回給客戶頁面。上面只是簡單演示,你也可以使用上面的方法來控制設備(比如點亮個燈、開合繼電器等)。
常用方法
WebServer(int port = 80)
構造方法;
void begin()
void begin(uint16_t port)
服務器啟動監聽;
void handleClient();
處理來自客戶端的請求;
void close()
void stop()
停止當前監聽;
bool authenticate(const char * username, const char * password)
校驗用戶是否登錄;
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String(""))
請求進行用戶登錄認證(瀏覽器端會打開登錄窗口);
void on(const String &uri, THandlerFunction handler)
void on(const String &uri, HTTPMethod method, THandlerFunction fn)
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn)
注冊鏈接與回調函數;
void onNotFound(THandlerFunction fn)
注冊未注冊鏈接回調函數
void onFileUpload(THandlerFunction fn)
注冊文件上傳回調函數;
void send(int code, const char* content_type = NULL, const String& content = String(""))
void send(int code, char* content_type, const String& content)
void send(int code, const String& content_type, const String& content)
向客戶端(瀏覽器)發送數據;
void sendHeader(const String& name, const String& value, bool first = false)
發送響應頭;
void sendContent(const String& content)
發送響應正文內容;
String uri()
返回客戶端請求的url;
HTTPMethod method()
返回客戶端請求方法
String pathArg(unsigned int i)
通過序號獲取路徑參數;
template<typename T>
size_t streamFile(T &file, const String& contentType)
發送文件,返回發送的字節數;
其它說明
HTTP狀態碼說明
上面的200、404等是HTTP狀態碼。用戶在請求訪問某個地址的時候,WebServer需要進行響應,發送響應頭,響應頭中第一行一般像是這樣的HTTP/1.1 200 OK,其中200就是狀態碼。常見的狀態碼如下:
200服務器成功返回網頁;
404請求的網頁不存在;
301本網頁被永久性轉移到另一個URL;
503服務器目前不可用;
401請求未經授權,需要登錄認證;
……
Content-Type說明
上面的text/plain、text/html這些是Content-Type,具體說明如下:
Content-Type(MediaType),即是Internet Media Type,互聯網媒體類型,也叫做MIME類型。在互聯網中有成百上千中不同的數據類型,HTTP在傳輸數據對象時會為他們打上稱為MIME的數據格式標簽,用於區分數據類型。最初MIME是用於電子郵件系統的,后來HTTP也采用了這一方案。
在HTTP協議消息頭中,使用Content-Type來表示請求和響應中的媒體類型信息。它用來告訴服務端如何處理請求的數據,以及告訴客戶端(一般是瀏覽器)如何解析響應的數據,比如顯示圖片,解析並展示html等等。
Content-Type舉例:
text/plain純文本文件;
text/htmlhtml文件;
application/jsonjson形式數據文件;
application/xmlxml形式數據文件;
image/pngpng格式圖片;
……
總結
WebServer的使用主要就是上面這些了,其它的一些相功能(DNS服務器、將網頁數據存儲在SD卡、通過網頁更新設備固件等)會在后面單獨寫文章進行介紹。
更多內容可以參考下面:
https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
https://www.cnblogs.com/testyao/p/6548261.html
除了官方自帶的WebServer庫外還有第三方的庫ESPAsyncWebServer可以使用,相關內容可以參考下面文章:
《Arduino for ESP8266&ESP32適用庫ESPAsyncWebServer:快速入門》
《Arduino for ESP8266&ESP32適用庫ESPAsyncWebServer:事件處理綁定》
《Arduino for ESP8266&ESP32適用庫ESPAsyncWebServer:請求與響應》
《Arduino for ESP8266&ESP32適用庫ESPAsyncWebServer:靜態文件和模板引擎》
《Arduino for ESP8266&ESP32適用庫ESPAsyncWebServer:WebSocket和EventSource》