httpsrv - HTTP 服务端
作者:拓毅恒
一、概述
httpsrv 核心库允许在 LuatOS 平台上创建一个轻量级的 HTTP 服务器,用于处理来自客户端的 HTTP 请求。该模块支持基本的 HTTP 方法(GET、POST、PUT、DELETE 等),并可以处理静态文件和动态请求。
支持网络适配器包括:
- WiFi AP 模式( socket.LWIP_AP ):适用于本地设备通过连接 WiFi 热点访问局域网内的 HTTP 服务器
- WiFi STA 模式:设备作为客户端连接到外部 WiFi 网络后启动 HTTP 服务器
- 以太网模式( socket.LWIP_ETH ):在支持以太网的设备上使用
需要注意的是:HTTP 服务器同一时间最多支持 1 个客户端连接,最多支持同时启动 16 个独立的 HTTP 服务实例
LuatOS 提供的 httpsrv 核心库适合用于本地设备调试、简单的 Web 控制界面、数据上报接口等场景,为设备提供便捷的 Web 访问能力。
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/httpsrv
function http_server_callback(fd, method, uri, headers, body)
log.info("httpsrv", method, uri, json.encode(headers), body)
-- /led是控制灯的API
if uri == "/led/1" then
LEDA(1)
return 200, {}, "ok"
elseif uri == "/led/0" then
LEDA(0)
return 200, {}, "ok"
-- 扫描AP
elseif uri == "/scan/go" then
wlan.scan()
return 200, {}, "ok"
-- 前端获取AP列表
elseif uri == "/scan/list" then
return 200, {["Content-Type"]="applaction/json"}, (json.encode({data=_G.scan_result, ok=true}))
-- 前端填好了ssid和密码, 那就连接吧
elseif uri == "/connect" then
if method == "POST" and body and #body > 2 then
local jdata = json.decode(body)
if jdata and jdata.ssid then
-- 开启一个定时器联网, 否则这个情况可能会联网完成后才执行完
sys.timerStart(wlan.connect, 500, jdata.ssid, jdata.passwd)
return 200, {}, "ok"
end
end
return 400, {}, "ok"
-- 根据ip地址来判断是否已经连接成功
elseif uri == "/connok" then
return 200, {["Content-Type"]="applaction/json"}, json.encode({ip=socket.localIP()})
end
-- 其他情况就是找不到了
return 404, {}, "Not Found" .. uri
end
httpsrv.start(80, http_server_callback, socket.LWIP_AP)
log.info("web", "pls open url http://192.168.4.1/")
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;
如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;
当前 httpsrv 核心库暂无专用常量定义,可结合 socket 核心库的常量使用。
四、函数详解
httpsrv.start(port, func, adapter)
功能
启动并监听一个 HTTP 端口,处理来自客户端的 HTTP 请求。
参数
port
参数含义:HTTP服务器监听的端口号;
数据类型:number;
取值范围:1-65535;
是否必选:必须传入此参数;
注意事项:请确保端口未被其他程序占用,最多支持同时启动16个独立的 HTTP 服务实例,但HTTP 服务器同一时间最多支持1个客户端连接;
参数示例:80 表示监听80端口;
func
参数含义:HTTP请求处理回调函数;当收到HTTP请求时会被调用,回调函数接收客户端连接对象、请求方法、请求URI、请求头和请求体等参数,并可以返回响应状态码、响应头和响应体。
数据类型:function;
取值范围:无特别限制;
是否必选:必须传入此参数;
注意事项:回调函数会在每次收到HTTP请求时被调用;回调函数内部无法使用sys.wait(timeout)、sys.waitUntil(msg, timeout)、sys.waitMsg(task_name, msg, timeout)等必须用在task中的函数。
回调函数返回值说明:回调函数可以返回三个值,用于构造HTTP响应:
1. HTTP状态码:number类型,如200表示成功,404表示未找到资源等;
2. 响应头:table类型,包含HTTP响应头信息;
3. 响应体:string类型,包含HTTP响应体内容;
如果回调函数没有返回值,则默认返回404, {}, ""。
参数示例:如下所示,定义了一个完整的HTTP请求处理回调函数,该函数可以接收HTTP请求并返回响应:
function http_server_callback(client, method, uri, headers, body)
-- client:客户端连接对象,userdata类型,一般无需直接操作
-- method:HTTP请求方法,string类型,如"GET"、"POST"等
-- uri:请求URI,string类型,如"/api/data"、"/index.html"等
-- headers:请求头,table类型,以键值对形式存储,如headers["content-type"]
-- body:请求体,string类型,如JSON字符串、表单数据等
log.info("HTTP请求", method, uri)
-- 返回HTTP状态码、响应头和响应体
return 200, {["Content-Type"] = "application/json"}, '"status":"success"'
end
adapter
参数含义:网络适配器编号;
数据类型:number;
取值范围:根据具体产品不同,支持的网络适配器编号不同;
是否必选:可选传入此参数;如果没有传入此参数或者传入了nil类型,则使用默认值,即产品自带的网络协议栈;
注意事项:可用于指定使用WiFi、以太网等不同网络接口,详情见httpsrv核心库中常量;
参数示例:socket.LWIP_AP 表示使用AP模式下的LWIP协议栈;
返回值
local result = httpsrv.start(port, func, adapter)
result
含义说明:HTTP服务器启动是否成功;
数据类型:boolean;
取值范围:true 表示成功,false 表示失败;
注意事项:暂无;
返回示例:true 表示服务器启动成功;
回调函数参数说明
当收到 HTTP 请求时,传入的回调函数会被调用,并接收以下参数:
client
参数含义:客户端连接对象;
数据类型:userdata;
注意事项:一般情况下无需直接操作此对象;
method
参数含义:HTTP请求方法;
数据类型:string;
取值范围:"GET"、"POST"、"PUT"等;
uri
参数含义:HTTP请求的统一资源标识符;
数据类型:string;
取值范围:无特别限制;
示例:"/"、"/api/data"、"/index.html";
headers
参数含义:HTTP请求头信息;
数据类型:table;
取值范围:无特别限制;
示例:{["Content-Type"] = "application/json", ["User-Agent"] = "Mozilla/5.0"};
body
参数含义:HTTP请求体内容;
数据类型:string;
取值范围:无特别限制;
注意事项:对于GET请求,此参数通常为空字符串;
静态文件处理说明
HTTP 服务端模块支持自动处理静态文件请求:
- 当请求 URI 为 "/" 时,会自动映射为 /index.html,只要 /luadb/index.html 存在,浏览器输入服务器地址(如 http://192.168.4.1)即可直接打开首页。
- 如果需要映射到其他目录,若要访问其他页面,请在 URI 中给出完整的文件名,例如 http://192.168.4.1/explorer.html,服务器会查找 /luadb/explorer.html,如果找不到,会再尝试 /luadb/explorer.html.gz,存在时自动以 gzip 编码返回。
- 下载设备内的任意文件(如 123.txt)时,请使用单层路径,例如 http://192.168.4.1/123.txt,服务器仅查找 /luadb/123.txt 及 /luadb/123.txt.gz;
- 把文件放在“子目录”形式的 URI(如 /explorer.html/123.txt)不会触发 gzip 重试规则,且要求 /luadb/explorer.html/123.txt 必须真实存在。
- 当前默认查找 "/luadb/xxx" 下的文件,随后找 "/" 下的文件,此路径暂不可配置
示例
-- 基本用法:启动HTTP服务器监听80端口
httpsrv.start(80, function(client, method, uri, headers, body)
log.info("httpsrv", method, uri)
return 200, {["Content-Type"] = "text/html"}, "<h1>Hello, World!</h1>"
end)
-- 指定网络适配器的用法
httpsrv.start(8080, function(client, method, uri, headers, body)
return 200, {}, "Response from specific adapter"
end, socket.LWIP_AP)
httpsrv.stop(port, no_used, adapter)
功能
停止指定端口上的 HTTP 服务。
参数
port
参数含义:要停止的HTTP服务器监听的端口号;
数据类型:number;
取值范围:1-65535;
是否必选:必须传入此参数;
注意事项:必须与之前调用httpsrv.start时使用的端口号一致;
参数示例:80 表示停止监听80端口的HTTP服务器;
no_used
参数含义:占位参数,暂无实际用途;
数据类型:nil;
取值范围:固定为nil;
是否必选:必须传入此参数;
注意事项:调用时请固定写nil;
参数示例:nil;
adapter
参数含义:网络适配器编号;
数据类型:number;
取值范围:根据具体产品不同,支持的网络适配器编号不同;
是否必选:必须传入此参数;
注意事项:必须与之前调用httpsrv.start时使用的网络适配器一致;
参数示例:socket.LWIP_AP 表示停止使用AP模式下LWIP协议栈的HTTP服务器;
返回值
local result = httpsrv.stop(port, no_used, adapter)
result
含义说明:HTTP服务器停止是否成功;
数据类型:boolean;
取值范围:true 表示成功,false 表示失败;
注意事项:暂无;
返回示例:true 表示服务器停止成功;
示例
-- 停止监听80端口的HTTP服务器
local ret = httpsrv.stop(80, nil, socket.LWIP_AP)
log.info("httpsrv", "服务器停止结果", ret)
五、注意事项
- 资源占用:HTTP 服务器会占用一定的系统资源,请根据设备实际情况合理使用;
- 并发处理:当前版本的 HTTP 服务端模块对并发请求的处理能力有限,建议在低并发场景下使用;
- 安全考虑:该模块不提供复杂的安全功能(如 HTTPS、身份验证等),在生产环境中使用时请注意安全防护;
- 静态文件路径:当前静态文件默认查找路径为"/luadb/",随后找 "/" 下的文件,此路径暂不可配置;
- 端口占用:请确保使用的端口未被其他程序占用,避免端口冲突;
- 网络适配器:在使用多网络接口的设备上,请确保正确指定网络适配器参数;
六、总体使用说明
在 LuatOS 平台上使用 httpsrv 模块构建 HTTP 服务器时,整体使用流程和不同网络环境下的配置如下:
1. 确定网络适配器类型
httpsrv 模块支持多种网络适配器,您需要根据实际使用场景选择合适的适配器:
- WiFi AP 模式:通过
socket.LWIP_AP参数指定,适用于设备作为 WiFi 热点时提供 Web 服务 - WiFi STA 模式:设备作为客户端连接到外部 WiFi 网络后启动 HTTP 服务器
- 以太网模式:通过
socket.LWIP_ETH参数指定,仅适用于支持以太网接口的设备
2. 不同网卡下的使用方法
2.1 WiFi AP 模式
-- 配置 WiFi AP 模式
wlan.init()
wlan.createAP("LuatOS-AP", "12345678")
-- 配置IP
netdrv.ipv4(socket.LWIP_AP, "192.168.4.1", "255.255.255.0", "0.0.0.0")
-- 等待网络准备就绪
while netdrv.ready(socket.LWIP_AP) ~= true do
sys.wait(100)
end
-- 设置DNS代理
dnsproxy.setup(socket.LWIP_AP, socket.LWIP_GP)
-- 创建DHCP服务器
dhcpsrv.create({adapter=socket.LWIP_AP})
-- 在 WiFi AP 模式下启动 HTTP 服务器
local result = httpsrv.start(80, function(client, method, uri, headers, body)
-- 处理请求逻辑
return 200, {["Content-Type"] = "text/html"}, "<h1>Hello from WiFi AP!</h1>"
end, socket.LWIP_AP)
if result then
-- WiFi AP 默认IP为 192.168.4.1
log.info("httpsrv", "WiFi AP server started at http://192.168.4.1:80")
else
log.error("httpsrv", "Failed to start server on WiFi AP")
end
2.2 WiFi STA 模式
-- 配置 WiFi STA 模式
wlan.init()
local connect_result = wlan.connect("kfyy123", "kfyy123456")
log.info("WiFi连接请求结果: ", connect_result)
function handle_http_request(fd, method, uri, headers, body)
log.info("httpsrv", method, uri, json.encode(headers), body)
if uri == "/led/1" then
LEDA(1)
return 200, {}, "ok"
elseif uri == "/led/0" then
LEDA(0)
return 200, {}, "ok"
elseif uri == "/scan/go" then
wlan.scan()
return 200, {}, "ok"
elseif uri == "/scan/list" then
return 200, {["Content-Type"]="application/json"}, (json.encode({data=_G.scan_result, ok=true}))
elseif uri == "/connect" then
if method == "POST" and body and #body > 2 then
local jdata = json.decode(body)
if jdata and jdata.ssid then
sys.timerStart(wlan.connect, 500, jdata.ssid, jdata.passwd)
return 200, {}, "ok"
end
end
return 400, {}, "ok"
elseif uri == "/connok" then
-- 获取默认网络协议栈的IP地址
local ip = socket.localIP()
log.info("connok", json.encode({ip=ip}))
return 200, {["Content-Type"]="application/json"}, json.encode({ip=ip})
end
return 404, {}, "Not Found" .. uri
end
function start_http_server()
-- 获取WiFi STA接口的IP地址
local ip = socket.localIP(socket.LWIP_STA)
if ip and ip ~= "0.0.0.0" then
-- 使用WiFi STA接口启动HTTP服务器
local result = httpsrv.start(80, handle_http_request, socket.LWIP_STA)
if result then
log.info("web", "HTTP server started on WiFi STA at http://" .. ip .. ":80")
return true
else
log.error("web", "Failed to start HTTP server on WiFi STA interface")
end
else
log.warning("web", "WiFi STA interface not available or no IP address")
end
return false
end
sys.subscribe("IP_READY", start_http_server)
2.3 以太网模式
-- 在以太网模式下启动 HTTP 服务器(仅支持以太网的设备)
local result = httpsrv.start(80, function(client, method, uri, headers, body)
-- 处理请求逻辑
return 200, {["Content-Type"] = "text/html"}, "<h1>Hello from Ethernet!</h1>"
end, socket.LWIP_ETH)
if result then
-- 获取本地IP地址(以太网)
local ip = socket.localIP(socket.LWIP_ETH)
log.info("httpsrv", "Ethernet server started at http://" .. ip .. ":80")
else
log.error("httpsrv", "Failed to start server on Ethernet")
end
3. 服务器 IP 地址获取与使用
httpsrv 模块本身不提供直接设置服务器 IP 地址的功能,服务器会自动绑定到所选网络适配器的所有可用 IP 地址上。您可以通过以下方式获取服务器的 IP 地址:
-- 获取指定网络适配器的 IP 地址
local ap_ip = socket.localIP(socket.LWIP_AP) -- WiFi AP 模式
local sta_ip = socket.localIP(socket.LWIP_STA) -- WiFi STA 模式
local eth_ip = socket.localIP(socket.LWIP_ETH) -- 以太网模式
客户端需要通过这些 IP 地址来访问 HTTP 服务器。在实际应用中,建议将获取到的 IP 地址通过日志输出或显示在屏幕上,方便用户访问。
4. 多端口同时服务
httpsrv 模块支持同时启动多个 HTTP 服务实例(最多 16 个),可以在不同端口上提供不同的服务内容:
-- 在 80 端口启动 Web 控制界面服务
httpsrv.start(80, web_control_callback, socket.LWIP_AP)
-- 在 8080 端口启动数据接口服务
httpsrv.start(8080, data_api_callback, socket.LWIP_AP)
5. 服务停止
当不再需要 HTTP 服务时,应及时停止以释放系统资源:
-- 停止指定端口和适配器上的 HTTP 服务
local result = httpsrv.stop(80, nil, socket.LWIP_AP)
if result then
log.info("httpsrv", "Server stopped successfully")
else
log.error("httpsrv", "Failed to stop server")
end
七、使用流程框图
流程说明
- 初始化网络:根据需要使用的网络适配器类型,进行相应的网络初始化配置(如设置 WiFi AP 参数)
- 创建回调函数:编写 HTTP 请求处理函数,该函数将在每次收到客户端请求时被调用
- 选择网络适配器:确定使用哪种网络适配器(WiFi AP、STA 或以太网)
- 启动服务:调用
httpsrv.start()函数启动 HTTP 服务器 - 检查启动结果:根据返回值判断服务器是否成功启动
- 获取并记录 IP 地址:获取服务器的 IP 地址,并通过日志或其他方式告知用户
- 处理请求:服务器开始处理来自客户端的 HTTP 请求,调用之前定义的回调函数
- 停止服务:当不再需要 HTTP 服务时,调用
httpsrv.stop()函数停止服务,释放资源
八、产品支持说明
适用于 Air8000,Air8000A,Air8000W,Air8000XB,Air8000U,Air8000WU,Air8000N,Air8000WN,以及 Air6101/Air8101 系列 此类支持蓝牙功能的产品。