关于网络协议 HTTP 3 之web框架
这一节应该算外传的性质。得到qq群里会飞的小猪
启发,打算写一个web框架,处理比较复杂的服务器响应。
之前的例程中,Html和图片的代码都是嵌入到程序中。而一般情况中,Html文档和图片都是单独存放的。
所谓web框架
- ,简单说,就是接收客户端的HTTP请求,返回相应的资源,可以是另一个html文件,或者是图片等等。
下面给的例程是一个非常简单的框架,只处理GET
请求,服务器由两个html
文档,一个png
图片,一个服务器图标favicon.ico
组成。
- 主页
Home.html
嵌入png
图片 -
LED.html
可以控制esp-12
的LED - 两个
html
文档都包含彼此的链接 -
favicon.ico
出现在标签页上,收藏时作为标识
接上节
Step 5
先分析服务器代码,名称为web_frame.lua
SSID = " "
password = " "
pin = 4
-- wifi.setmode(wifi.STATION)
wifi.sta.config(SSID,password)
gpio.mode(pin, gpio.OUTPUT)
srv=net.createServer(net.TCP,30)
srv:listen(80,function(conn)
conn:on("receive", function(conn,payload)
print(payload)
local _, _, method, vars = string.find(payload, "([A-Z]+) /(.+) HTTP");
print(method,vars);
local filename = nil
if (vars == nil) then filename = "Home.html"
elseif (vars ~= nil) then
local _, _, key, value = string.find(vars,".*?(%w+)=(%w+).*")
if (value ~= nil) then
if (value =="ON4") then gpio.write(pin, gpio.HIGH);
elseif (value =="OFF4") then gpio.write(pin, gpio.LOW);
end
filename = "LED.html"
elseif (value == nil) then filename = vars
end
end
print(filename)
file.open(filename,"r")
local length = file.seek("end")
file.seek("set")
local function send ()
if (file.seek("cur") == length ) then
conn:close()
file.close()
print(filename.." has been sent.")
else
local buf2 = file.read(1024)
conn:send(buf2)
print(file.seek("cur"))
end
end
send()
conn:on("sent", send)
end)
end)
print(wifi.sta.getip())
- 见过很多次的代码就不详细解释了,首先设定wifi账号和密码,设定LED的gpio管脚,连接路由,设置gpio为输出模式,创建TCP服务器,延迟30秒。然后监听HTTP端口80,注册
recieve
事件,接收client的请求 - 解析
client
请求的第一行,因为nodemcu的文件系统不支持文件夹,所以路径没有用,只解析method
和客户端的返回值vars
-
vars
分为几种情况,下面的代码进行解析- 如果客户端刚连上服务器(在浏览器地址栏输入nodemcu的IP,回车),
payload
是GET / HTTP/1.1
,vars
为空,定义发送的文件名称filename
为主页Home.html
- 如果
client
发出请求,vars
不为空 -
vars
可能是按钮发出的请求命令%22?pin=ON4\%22
,用string.find
将pin
和ON4
解析出来,赋值给变量key
和value
- 根据
value
的值,调用gpio函数,控制LED - 同时,filename =
LED.html
,发送LED.html
文档 如果
vars
是像Home.html
,nodemcu.png
这样的文件名,value
为空,filename = vars。把vars作为要发送的文件名上述代码定义了服务器要发送的文件,接下来打开filename文件读取
- file.seek(“end”)得到文件的长度,此时读取位置移到文件的末尾
file.seek(“set”)重新定位到文件开头
接下来定义本地函数
send()
,分段读取文件并发送,根据词法作用域,本地函数内部可直接调用上层函数的变量- file.seek(“cur”)是当前读取的位置,如果等于文件长度,代表已读取完毕,关闭filename文件,同时关闭conn连接
如果没有读取完毕,就读取1024个字符,并用conn:send()发送
调用本地函数
send()
,发送第一段文件片段-
conn:on("sent",send)
,注册了一个”sent”事件,每次发送一段后,就调用send()
发送第二段命令,第二段发送后又会再次调用发第三段,直到全部发送完毕,关闭conn。 - 上述过程是一个
send()
函数的递归调用 -
conn:on("sent",send)
只是注册事件,只有满足条件才会执行,它和send()
代码的顺序是可以颠倒的,参考代码里,send()就放在了后面。
- 如果客户端刚连上服务器(在浏览器地址栏输入nodemcu的IP,回车),
关于这段代码,多说几点
- 服务器发送的
respond
信息,也就是conn:send发送的信息,没有包括HTTP的头部信息,可能是因为懒吧 - 如果请求的文件不存在,程序会报错,比如你g忘了上传
favicon.ico
,程序会报错退出,因为我没有加判断机制,避免打开一个不存在的文件,也是因为懒吧 - 这种发送方式算是官方推荐的方式,可以发送很大的文件,这里的png文件有23k,大小远超出了空闲的内存,据qq群大佬们说,发送2M的文件都没有问题。
- 连续用conn:send发送,官方说是会报错的,我之前的例程就是用连续发送(傲娇脸),我自己的经验是:在传小于3~4k的文件还行,如果是传大图片就传不完了,官方的说法是乐鑫原厂固件的设定。所以还是推荐官方默认的方法
- 两个Html文档都是用上节的例程改的,这里就不解释了。我把这几个文件都和这篇文档一起上传了
Step 6
- 分别上传
web_frame.lua
,Home.html
,LED.html
,nodemcu.png
,favicon.ico
, 记得SSID和password改成自己路由器的 - dofile(“web_fram.lua”),在浏览器输入nodemcu的IP,如果没有给出,就手动输入
print(wifi.sta.getip())
,nodemcude的IP是第一个 - 主页界面是这样,下方有
LED.html
的链接
在标签上可以看到 favicon.ico
图标
-
点击下方的
LED.html
,跳转页面,点击按钮可以控制LED 这个服务器在firfox,IE edge,chrom上测试通过,但是360浏览器没法控制LED,也许任务我这个服务不安全,不返回”?pin=ON4”信息(神秘的微笑)