WebServer是非常常用的一个功能,在设备上使用该功能用户就可以不依赖app直接通过浏览器访问和操作设备。另外即使是用app的,对于app开发来说直接访问webapi也比处理tcp/udp要方便些。
WebServer简单点理解就是网页服务器,主要干的活就是用户访问链接的时候执行相应的动作,对于开发来说主要处理的就是注册链接并编写用户访问该链接时需要执行的操作。
使用步骤如下:
#include
;WebServer对象
并设置端口号,一般WebServer端口号使用80
;on()
方法注册链接与回调函数;begin()
方法启动服务器进行请求监听;handleClient()
处理来自客户端的请求;可以使用下面代码进行测试:
#include
#include //引入相应库
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(); //处理来自客户端的请求
}
如果你有很多相似的链接比如/user/1
、/user/2
、/user/3
、/user/4
……,使用上面的方法时就需要每个链接都需要进行声明注册,比较不方便,这里可以使用路径参数来处理这些相似的或是动态的链接,可以用下面的代码进行测试:
#include
#include //引入相应库
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(); //处理来自客户端的请求
}
如果用户访问了未注册的的链接时我们最好能给个提示,比如我们在上网时经常能见到的“网页不存在”、“404 Not Found”等。在这里我们可以用onNotFound()
方法来给出这样的提示,用户在访问不存在的链接时会跳转到该方法所绑定的回调函数上,可以用下面代码进行测试:
#include
#include //引入相应库
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(); //处理来自客户端的请求
}
用户认证可以提供一定的安全性,这里提供了BASIC_AUTH
和DIGEST_AUTH
两种方式,一般来说DIGEST_AUTH
方式安全性稍高些,下面代码进行了基本的测试:
#include
#include //引入相应库
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(); //处理来自客户端的请求
}
客户端请求链接,我们也能够知道客户端请求的一些信息,可以用下面代码进行测试:
#include
#include //引入相应库
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(); //处理来自客户端的请求
}
网页与后台数据交互需要用到网页和html和ajax知识,可以参考:《从零开始的ESP8266探索(06)-使用Server功能搭建Web Server》中《通过网页收发数据》章节
我们首先准备一个带ajax脚本的网页:
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 WebServer Testtitle>
<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">Unkwondiv>
<input type="button" value="random" onclick="getData()">
body>
html>
然后通过工具将它转换成字符串嵌入到代码中,工具可以参考:http://tools.jb51.net/transcoding/html2js
也可以先压缩网页然后再转换成字符串,这样可以减小网页大小,提高效率,可以参考:https://tool.lu/html/
下面就是带有 网页与后台数据交互功能 的完整代码:
#include
#include //引入相应库
//网页
String myhtmlPage =
String("") +
"" +
"" +
" ESP32 WebServer Test " +
" " +
"" +
"" +
" Unkwon" +
" " +
"" +
"";
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(); //处理来自客户端的请求
}
可以看到当点击网页上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()
HTTPMethod method()
String pathArg(unsigned int i)
template
size_t streamFile(T &file, const String& contentType)
上面的200
、404
等是HTTP状态码。用户在请求访问某个地址的时候,WebServer需要进行响应,发送响应头,响应头中第一行一般像是这样的HTTP/1.1 200 OK
,其中200
就是状态码。常见的状态码如下:
200
服务器成功返回网页;404
请求的网页不存在;301
本网页被永久性转移到另一个URL;503
服务器目前不可用;401
请求未经授权,需要登录认证;……
上面的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/html
html文件;application/json
json形式数据文件;application/xml
xml形式数据文件;image/png
png格式图片;……
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》