作者:朱天华
Hello,大家好,我是朱天华。
欢迎大家来到合宙LuatOS直播课堂,一起学习LuatOS课程。
第一部分:LuatOS课程背景
因为今天是我们LuatOS系列课程的第四讲,所以在这里我就不重复讲解整个LuatOS课程的背景了;
如果您还不清楚LuatOS课程背景,可以访问:LuatOS课程背景 这个链接,进行了解;
第二部分:LuatOS HTTP课程讲哪些内容
今天的LuatOS HTTP课程主要从以下几方面进行讲述:
首先,复习以下两项内容:
- TCP/IP总体介绍;
- LuatOS上的
4G/WiFi/以太网三种网络环境下的TCP/IP协议栈总体介绍;
以上这两部分理论知识的内容和LuatOS课程的第002讲 socket重复,所以在本讲中仅仅对一些核心知识点进行简单的复习回顾,同时重点说明一下这些理论知识和HTTP的关系;
如果你还不清楚这些理论知识内容,可以访问:LuatOS socket(第三部分到第五部分) 这个链接,再学习一下;
其次,从以下几点,重点学习下HTTP的理论知识:
- HTTP协议和交互流程分析;
- HTTP协议报文;
通过以上两点的学习,让大家对HTTP本身有一个详细的理解;
最后,基于LuatOS上的HTTP能力,从以下几点,重点学习下LuatOS HTTP的应用编程:
- LuatOS
http核心库和httpplus扩展库功能介绍; - LuatOS HTTP client应用开发示例;
- 客户在使用LuatOS HTTP client开发时遇到的一些问题分析;
第三部分:基于TCP/IP初步认识HTTP
今天我们讲的LuatOS HTTP是LuatOS开发中最常用到的网络应用之一,用户使用LuatOSHTTP开发网络应用,会接触到TCP/SSL/认证等一些网络核心概念;
而说到网络应用,就绕不开TCP/IP;
所以在本章节中,我们以HTTP为切入点,先来简单的回顾复习一下TCP/IP协议的核心知识;
如果你想了解更详细的TCP/IP知识,可以访问:LuatOS socket(第三部分到第五部分) 这个链接,自行学习;
3.1 HTTP在TCP/IP协议中的位置
| TCP/IP五层模型 | 协议实现 |
|---|---|
| 应用层 | HTTP,MQTT,FTP,WebSocket,DNS,NTP,SMTP等TLS |
| 传输层 | TCP,UDP等 |
| 网络层 | IP等 |
| 数据链路层 | 以太网,WiFi,4G 等网络各自的数据链路层协议 以太网的IEEE 802.3系列 WiFi网络的IEEE 802.11系列 4G网络的PDCP、RLC、MAC等协议 |
| 物理层 | 光纤,双绞线,无线电波等传输介质 |
上面这张表格描述的是TCP/IP五层模型,在这里大家关注一下黄色背景的内容,和本讲课程我们重点要学习的LuatOS HTTP关系比较大;
HTTP是一个应用层的协议,在传输层使用的是TCP协议;
在HTTP和TCP之间,根据是否支持TLS加密认证来划分,又可以分为非加密的HTTP和加密的HTTPS两大类;
加密的HTTPS根据支持的身份认证类型,又可以分为以下三类:
- 不支持身份认证的加密HTTPS;
- 支持单向身份认证的加密HTTPS,client验证server的身份;
- 支持双向身份认证的加密HTTPS,client和server互相验证对方的身份;
再往下看数据链路层,支持以太网,WiFi,4G等常见的数据链路承载,对于HTTP应用来说,并不关心数据链路层的承载是什么类型,只关心有一个可用的数据链路网络就行;
LuatOS软件支持4G,以太网,WiFi三种数据链路承载网络,并且也支持三种网络之间按照配置的优先级无感自动切换。
3.2 HTTP应用数据在TCP/IP数据包中的位置

上面这张图演示的是,应用层以HTTP为例,TCP/IP数据 封装/解封装的过程,一个完整的TCP/IP数据包如下图所示:

- 在HTTP client请求端,HTTP网络协议生成
HTTP应用数据; HTTP应用数据向下传,交给传输层,增加了传输层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源端口号和目标端口号,这两个端口号的作用是:定义了发送端和接收端的应用程序,例如HTTP服务器,如果提供未加密的服务,则目标端口号一般为80;如果提供加密的服务,则目标端口号一般为443;通过端口号,可以提供两个主机之上端到端的通信;传输层头部+HTTP应用数据向下传,交给网络层,增加了网络层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源IP地址和目标IP地址,这两个IP地址的作用是:定义了发送端和接收端的网络设备地址;可以提供主机到主机的通信;网络层头部+传输层头部+HTTP应用数据向下传,交给数据链路层,增加了数据链路层的头部信息;在这一层会根据不同的数据链路承载(例如4G网络,WiFi网络,以太网),增加不同的数据链路层头部;- 最终,
数据链路层头部+网络层头部+传输层头部+HTTP应用数据的数据包,通过物理层转换为0和1的电信号,然后通过物理传输介质发给了HTTP server端; - HTTP server端收到数据后,从下到上,依次解析并且剥离出数据链路层头部、网络层头部、传输层头部,最终将
HTTP应用数据交给HTTP应用程序去处理; - 接收端处理完应用数据后,如果需要返回应用数据给发送端;此时发送端和接收端的角色互换,再走一遍数据分层的封装和解封装过程;
接下来我使用LuatOS模拟器访问:http://httpbin.air32.cn/get
luatos --llt=H:\Luatools\project\Air8000-http.ini
实际上是:发送一个http get请求到"http://httpbin.air32.cn/get"(这是合宙提供的一个不能商用的http测试服务器),抓取了完整的http请求和http应答报文,我们来实际分析一下这两种报文,加深一下对http应用的完整TCP/IP数据包的理解;


接下来我实际操作演示一下这个过程;
讲到这里,我们对HTTP在TCP/IP协议栈中的层次位置应该有了一个初步的认识;
接下来,我们重点理解下HTTP协议本身的一些知识点;
第四部分:详细理解HTTP
这里有一篇在线文章:https://www.cnblogs.com/an-wen/p/11180076.html ;
详细的讲解了HTTP协议,大家如果没有接触过HTTP,可以自己打开看一下;
在本章节,我只介绍一些重要的内容;
4.1 HTTP是什么
HTTP 是 超文本传输协议(HyperText Transfer Protocol) 的缩写,是用于在客户端(如合宙支持数传的Air8000系列/Air780系列/Air8101系列模组、浏览器、手机 App)和服务器之间传输数据的核心规则,也是互联网运行的基础协议之一。
简单来说,当你在浏览器输入网址、刷网页、看视频时,背后都有 HTTP 在负责 “沟通”—— 客户端用 HTTP 告诉服务器 “我需要什么”,服务器用 HTTP 回复 “这是你要的内容”。
4.2 HTTP 核心架构:请求 - 响应 模型
4.2.1 两个角色

● 客户端(client):http请求的发起者;
● 服务器(server):http响应的处理者;
4.2.2 请求-响应的核心流程
HTTP 的 “请求 - 响应模型” 是其核心工作机制,简单来说就是:客户端主动发起请求,服务器接收并处理后返回响应,整个过程由客户端驱动,服务器被动响应。
整个核心流程分为6步,接下来我以访问 http://httpbin.air32.cn/get 为例,先来说明一下这个过程;
4.2.2.1 客户端发起请求前的准备工作
客户端发起请求前,需明确三个核心信息:
- 目标服务器:将 URL 中的域名
httpbin.air32.cn解析为 IP 地址(通过 DNS服务)。 - 请求资源:URL 中的路径
/get。 - 通信规则:使用 HTTP 还是 HTTPS,以及对应的服务器端口(80/443),本示例中使用的是http,没有指定端口,表示使用默认端口80。

4.2.2.2 客户端建立TCP连接(三次握手)
HTTP 依赖传输层协议TCP建立可靠连接,确保数据可靠传输:
- TCP 三次握手:客户端先向服务器发送连接请求,服务器确认,客户端再回应确认,最终建立双向通信通道(HTTPS 在此基础上还会增加 TLS/SSL 握手,完成加密协商)。
- 连接复用:HTTP/1.1 及以上支持 “长连接”
Connection: keep-alive,一个连接可处理多个请求,减少重复握手的开销。


4.2.2.3 客户端构造并发送 HTTP 请求报文
连接建立后,客户端按照 HTTP 协议格式组装请求报文,发送给服务器。请求报文包含 4 部分:
- 请求行:明确请求方法(如 GET/POST)、目标 URL、HTTP 版本(如 HTTP/1.1)。示例:
GET /get HTTP/1.1 - 请求头:附加信息(如客户端类型、期望数据格式、Cookie 等)。示例:
Host: ``httpbin.air32.cn、Connection: keep-alive - 空行:分隔请求头和请求体。
- 请求体:仅 POST/PUT 等方法需要,用于提交数据(如表单、JSON)。

4.2.2.4 服务器接收并处理请求
服务器(如 Nginx、Apache、Node.js 服务)通过监听端口(80/443)接收请求,处理流程包括:
- 解析请求报文:提取请求方法、资源路径、请求头和请求体,确定客户端需求(如 “获取首页 HTML”“提交登录数据”)。
- 执行业务逻辑:根据请求内容处理(如读取文件、查询数据库、验证权限)。
- 生成响应数据:将处理结果整理为客户端可识别的格式(如 HTML、JSON、图片二进制流)。
4.2.2.5 服务器返回 HTTP 响应报文
服务器按照 HTTP 协议格式组装响应报文,通过 TCP 连接返回给客户端。响应报文包含 4 部分:
- 状态行:明确 HTTP 版本、状态码(如 200/404)、状态描述(如 OK/Not Found)。示例:
HTTP/1.1 200 OK - 响应头:附加信息(如数据类型、长度、缓存规则)。示例:
Content-Type: application/json、Content-Length: 427 - 空行:分隔响应头和响应体。
- 响应体:实际返回的数据(如 HTML 代码、JSON 字符串、图片二进制数据)。

4.2.2.6 客户端处理响应并关闭连接(可选)
客户端接收响应后,进行后续处理:
- 解析响应报文:检查状态码(判断请求是否成功)、响应头(确定数据格式)。
- 处理响应体:浏览器渲染 HTML 为网页、App 解析 JSON 为数据对象、下载工具保存文件。
- 关闭连接:若无需继续请求,通过 TCP 四次挥手断开连接(长连接下可复用连接处理新请求)。
4.3 HTTP协议报文
HTTP 协议报文是客户端与服务器之间传递数据的标准化格式,分为请求报文(客户端发往服务器)和响应报文(服务器返回客户端)两类,二者结构相似但核心字段不同。
4.3.1 请求报文格式(客户端→服务器)
请求报文用于发起资源请求(如获取网页、提交表单),完整结构包含 4 个部分,按顺序排列如下:
4.3.1.1 请求行(Request Line)
请求行是报文的第一行,由 3 个部分组成,用空格分隔,末尾以CRLF(回车 + 换行)结束,格式为:
请求方法 + 请求URI + HTTP版本
| 组成部分 | 说明 | 示例值 |
|---|---|---|
| 请求方法 | 定义请求的操作类型,常用有 8 种(GET/POST/PUT/DELETE 等) | GET、POST |
| 请求 URI | 目标资源路径(可含查询参数) | /api/user?id=100 |
| HTTP 版本 | 使用的 HTTP 协议版本(HTTP/1.0、HTTP/1.1、HTTP/2) | HTTP/1.1 |
示例请求行:GET /index.html?name=test HTTP/1.1
4.3.1.2 请求头(Request Headers)
请求行之后是请求头,由多个 “键: 值” 对组成,每行一个键值对,键和值之间以: (此处有一个空格)分隔,末尾以CRLF(回车换行\r\n)结束,用于描述客户端信息、请求偏好、数据格式等。常见请求头字段如下:
| 常见请求头字段 | 作用 | 示例值 |
|---|---|---|
| Host | 目标服务器域名(HTTP/1.1 必选,用于虚拟主机区分) | Host: www.example.com |
| Content-Type | 请求体的数据格式(仅 POST/PUT 等带体请求使用) | Content-Type: application/json |
| Content-Length | 请求体的数据长度(单位:字节) | Content-Length: 44 |
| Connection | 控制 TCP 连接是否复用(keep-alive 表示长连接,close 表示断开) | Connection: keep-alive |
4.3.1.3 空行(Blank Line)
请求头之后必须有一个单独的CRLF(回车换行,即空行),用于明确分隔 “请求头” 和 “请求体”,是 HTTP 协议的强制规定 —— 无空行时,服务器会无法识别后续数据,导致请求失败。
4.3.1.4 请求体(Request Body):可选的提交数据
空行之后是请求体,仅在需要提交数据时存在(如 POST/PUT 请求),GET 请求无请求体。
请求体的格式由Content-Type头字段定义,常见格式及示例如下:
| 格式类型 | 说明 | 示例内容 |
|---|---|---|
| application/json | JSON 格式(API 请求常用) | {"username":"test","password":"123456"} |
| application/x-www-form-urlencoded | 表单默认格式(键值对,URL 编码) | username=test&password=123456 |
| multipart/form-data | 文件上传格式(支持多文件 + 普通字段) | 包含文件二进制流 + 字段边界标识 |
| text/plain | 纯文本格式 | Hello World |
4.3.1.5 报文示例
POST /api/login HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 44
Connection: keep-alive
{"username":"testuser","password":"testpass123"}
4.3.2 响应报文格式(服务器→客户端)
响应报文用于回复请求结果(如返回网页、数据或错误信息),结构与请求报文对称,同样包含 4 个部分:
4.3.2.1 状态行(Status Line)
状态行是报文的第一行,由 3 个部分组成,用空格分隔,末尾以CRLF(回车换行\r\n)结束;
格式为:HTTP版本 + 状态码 + 状态描述
| 组成部分 | 说明 | 示例值 |
|---|---|---|
| HTTP 版本 | 与请求报文一致(HTTP/1.1、HTTP/2 等) | HTTP/1.1 |
| 状态码 | 3 位数字,标识请求处理结果(1xx 信息 / 2xx 成功 / 3xx 重定向 / 4xx 客户端错 / 5xx 服务错) | 200、404、500 |
| 状态描述 | 对状态码的文字解释(语义与状态码绑定,可自定义但建议遵循标准) | OK、Not Found、Internal Server Error |
示例状态行:HTTP/1.1 200 OK、HTTP/1.1 404 Not Found
4.3.2.2 响应头(Response Headers)
状态行之后是响应头,同样为 “键:值” 对格式,用于描述服务器信息、响应数据格式等,常见字段如下:
| 常见响应头字段 | 作用 | 示例值 |
|---|---|---|
| Content-Type | 响应体的数据格式(如 HTML、JSON、图片),含字符编码 | Content-Type: text/html; charset=utf-8 |
| Content-Length | 响应体的字节长度(帮助客户端判断数据是否接收完整) | Content-Length: 2048 |
| Date | 服务器生成响应的时间(GMT 格式) | Date: Wed, 29 Oct 2025 12:00:00 GMT |
| Location | 3xx 重定向时,指定新的资源地址 | Location: https://www.new-example.com |
4.3.2.3 空行(Blank Line)
与请求报文一致,响应头之后必须有一个单独的CRLF,分隔 “响应头” 和 “响应体”,无空行将导致客户端解析失败。
4.3.2.4 响应体(Response Body)
空行之后是响应体,包含服务器处理请求后返回的具体内容,格式由Content-Type头字段定义,常见场景如下:
| 场景 | Content-Type 示例 | 响应体内容示例 |
|---|---|---|
| 网页返回 | text/html | Hello |
| API 数据返回 | application/json | {"code":0,"msg":"success","data":{"id":100}} |
| 图片返回 | image/jpeg | 图片二进制流(无法直接显示,需解码) |
| 文件下载 | application/octet-stream | 二进制文件流(如.exe、.zip) |
4.3.2.5 完整响应报文示例
HTTP/1.1 200 OK
Server: Nginx/1.20.1
Date: Wed, 29 Oct 2025 12:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 58
{"code":0,"msg":"登录成功","data":{"username":"testuser","token":"abc789"}}
第五部分:基于HTTP理解LuatOS上的TCP/IP协议栈
在总体了解了HTTP的核心理论知识之后,接下来我们一起看下LuatOS上对TCP/IP协议栈的支持情况,以及提供了哪些编程接口给LuatOS项目应用脚本来使用,参考下表:
| TCP/IP五层模型 | LuatOS支持的TCP/IP协议 | LuatOS上的编程接口 | 备注 |
|---|---|---|---|
| 应用层 | HTTP,MQTT,FTP,WebSocket,DNS,NTP,DHCP SSL/TLS:从OSI七层模型来看,和表示层最接近,所以此处把SSL/TLS放到TCP/IP模型中应用层 |
socket核心库/libnet扩展库 http核心库/httpplus扩展库/httpsrv核心库 mqtt核心库 ftp核心库 websocket核心库 httpdns扩展库 dhcpsrv扩展库 udpsrv扩展库 |
应用层提供的这些编程接口和LuatOS项目应用开发关系最为密切; 这些核心库和扩展库的API文档参考: LuatOS API手册 在后续直播课程中,我们会逐一进行讲解 |
| 传输层 | TCP,UDP | socket核心库/libnet扩展库 | |
| 网络层 | IP,ICMP | socket核心库 exnetif扩展库 "IP_READY"、"IP_LOSE" |
在这里重点说一下exnetif扩展库,exnetif扩展库有两项核心功能: 1、LuatOS产品做为设备模式来使用,可以配置使用4G网卡,以太网网卡,WiFi网卡中的一种或者多种来上网,使用多种网卡上网时,可以配置使用的多种网卡优先级; 2、LuatOS产品做为路由器模式来使用,支持以下三大类网络路由场景: (1) 4G网卡连接外网,WiFi设备通过WiFi AP热点接入上网,以太网设备通过LAN口接入上网; (2) WiFi网卡连接外网,WiFi设备通过WiFi AP热点接入上网,以太网设备通过LAN口接入上网; (3) 以太网WAN口连接外网,WiFi设备通过WiFi AP热点接入上网,以太网设备通过LAN口接入上网; |
| 数据链路层 | 以太网,WiFi,4G 等网络各自的数据链路层协议 以太网的IEEE 802.3系列 WiFi网络的IEEE 802.11系列 4G网络的PDCP、RLC、MAC等协议 |
||
| 物理层 | 双绞线,无线电波等传输介质 |
具体到LuatOS HTTP应用编程,我们仅需要关注上表中黄色背景的几部分:
- http核心库和httpplus扩展库:这两个库是LuatOS HTTP client应用编程的核心,在下一章节我们会重点讲解;
- socket核心库、"IP_READY"和"IP_LOSE"消息:在LuatOS HTTP应用开发中,会主动查询网卡的状态,或者被动等待网卡状态的变化通知,这部分功能就会用到socket核心库、"IP_READY"和"IP_LOSE"消息;在本讲最后我们学习LuatOS http client开发示例时,再结合具体的代码演示如何使用这部分功能;
- exnetif扩展库:在LuatOS HTTP应用开发中,会使用到单网卡(4G 、WiFi、以太网网卡中的一种)或者多网卡(4G 、WiFi、以太网网卡中的多种),exnetif扩展库可以既简捷又灵活的配置各种网卡,让HTTP应用编程和网卡管理完全解耦;在本讲最后我们学习LuatOS http client开发示例时,我们结合具体的代码来分析exnetif扩展库如何使用;
第六部分:LuatOS上的http核心库和httpplus扩展库
LuatOS提供了http核心库和httpplus扩展库实现了http客户端
6.1 http核心库
http.request(method, url, headers, body, opts, server_ca_cert, client_cert, client_key, client_password)
功能
http 客户端,发送http请求到服务器,并且接收服务器应答;
使用时在最后加上一个.wait()方法的调用,使用方式为http.request().wait(),表示阻塞等待返回结果;
只能在task中使用,会阻塞当前task,等待整个过程成功结束或者出现错误异常结束或者超时结束,超时时长和opts.timeout有关;
参数
method
参数含义:HTTP请求方法;
数据类型:string;
取值范围:仅支持"GET"、"POST"、"PUT"请求方法,请求方法用大写字母表示;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:GET请求时填"GET",POST请求时填"POST";
url
参数含义:HTTP请求的URL地址;
数据类型:string;
取值范围:支持HTTP、HTTPS,支持域名、IP地址,支持自定义端口,标准的HTTP URL格式都支持;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"https://www.luatos.com/"
完整的 HTTP 请求 URL 格式是 统一资源定位符(URL) 的标准结构,用于精准定位互联网中的资源,核心格式可拆分为 7 个部分,部分字段在特定场景下可省略。
完整格式的通用表达式为:
协议方案://[用户信息@]主机名[:端口号]/路径?查询参数#片段标识符
1、协议方案(Scheme):必选
-
支持
http://和https://两种,必须以 “://” 结尾; -
使用 HTTP 协议时,表示明文传输,默认服务器端口 80;
-
使用 HTTPS 协议时,表示加密传输,默认服务器端口 443;
2、用户信息(User Info):可选
-
包含用户名和密码,用于身份验证,格式为 “用户名:密码”,后跟 “@” 分隔主机名;
-
示例:
user:pass@(表示用 “user” 账号、“pass” 密码访问后续主机);
3、主机名(Host):必选
-
资源所在服务器的标识,可是域名或IP 地址,是 URL 的核心部分,不可省略;
-
域名示例:
www.example.com(常见形式,需通过 DNS 解析为 IP); -
IP 地址示例:
49.168.5.68(直接使用服务器 IP,无需解析);
4、端口号(Port):可选
-
指定服务器上的服务端口,以 “:” 开头,若使用协议默认端口则可省略;
-
HTTP 默认端口:
80(如http://www.example.com等价于http://www.example.com:80); -
HTTPS 默认端口:
443(如https://www.example.com等价于https://www.example.com:443); -
自定义端口示例:
8080(如http://www.example.com:8080,常见于开发环境);
5、路径(Path):可选(但通常存在)
-
资源在服务器上的具体存储路径,以 “/” 开头,可多层级(用 “/” 分隔目录),若省略则默认访问服务器根路径(
/); -
单级路径示例:
/index.html(访问服务器根目录下的index.html文件); -
多级路径示例:
/api/user/list(访问服务器api/user目录下的list接口);
6、查询参数(Query):可选
-
向服务器传递的额外参数,以 “?” 开头,参数间用 “&” 分隔,格式为 “键 = 值”(值需 URL 编码,如空格转
%20、中文转%E4%B8%AD,LuatOS提供了string.urlEncode接口来生成URL编码); -
示例:
?id=100&name=test(表示传递两个参数:id=100和name=test); -
编码示例:
?keyword=HTTP%20URL(“HTTP URL” 中的空格编码为%20);
7、片段标识符(Fragment):可选
- 用于定位资源内部的特定位置(如网页内的锚点),以 “#” 开头,仅在客户端生效(不会发送到服务器);这个字段一般用在PC上的http浏览器中,例如浏览器请求一个网页,网页内容中有很多标题,请求时可以通过这个片段

- 示例:
#section1(表示跳转到网页中id="section1"的元素位置); - 注意:服务器接收 URL 时,会忽略
#及之后的内容,仅处理前面部分;
headers
参数含义:HTTP请求头;
数据类型:table或者nil;
取值范围:当为table数据类型时,支持键值对格式的任何请求头;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:{
["Content-Type"] = "application/x-www-form-urlencoded",
["self_defined_key"] = "self_defined_value"
}
body
参数含义:HTTP请求体;
数据类型:string或者zbuff或者nil;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:如果请求体是一个文件中的内容,需要把文件内容读出来,赋值给body使用;
参数示例:"123456" 或者 一个zbuff对象 或者 nil
opts
参数含义:HTTP请求的一些额外配置;参数为table类型时,table内容格式说明如下:
{
-- 参数含义:从发送请求到收到服务器响应,整个过程的超时时长,单位毫秒;
-- 数据类型:number或者nil;
-- 取值范围:number类型时,取值范围为大于等于0的整数,0表示永久等待;
-- 是否必选:可选传入此参数,默认值为10分钟;
-- 注意事项:暂无;
-- 参数示例:例如20000表示超时时长20秒;
timeout = ,
-- 参数含义:当HTTP请求接收到的body数据需要保存到本地文件中时,此参数表示完整的文件路径;
-- 数据类型:string或者nil;
-- 取值范围:无特别限制;
-- 是否必选:可选传入此参数;
-- 注意事项:保存数据到文件中时,需要自行保证这个文件存在;
-- 参数示例:"/download/1.txt";
dst = ,
-- 参数含义:上网使用的网卡ID;
-- 数据类型:number或者nil;
-- 取值范围:number类型时,取值范围参考socket api中的常量详解;
-- 是否必选:可选传入此参数;
-- 注意事项:如果没有传入此参数,内核固件会自动选择当前时间点其他功能模块设置的默认网卡;
-- 除非你HTTP请求时,一定要使用某一种网卡,才设置此参数;
-- 如果没什么特别要求,不要设置此参数,使用系统中设置的默认网卡即可 ;
-- 一般来说,LuatOS的网络应用demo中都会有netdrv_device功能模块设置默认网卡;
-- 所以建议使用http.request接口时,不要设置此参数,直接使用netdrv_device设置的默认网卡就行;
-- 参数示例:socket.LWIP_GP表示使用4G网卡;
adapter = ,
-- 参数含义:是否打开debug调试信息日志的开关;
-- 数据类型:boolean或者nil;
-- 取值范围:boolean类型时,true表示打开,false表示关闭;
-- 是否必选:可选传入此参数,默认值为false;
-- 注意事项:暂无;
-- 参数示例:false;
debug = ,
-- 参数含义:HTTP请求过程中是否使用ipv6;
-- 数据类型:boolean或者nil;
-- 取值范围:boolean类型时,true表示使用ipv6,false表示不使用ipv6;
-- 是否必选:可选传入此参数,默认值为false;
-- 注意事项:暂无;
-- 参数示例:false;
ipv6 = ,
-- 参数含义:接收HTTP应答body数据过程中的回调函数;回调函数的格式为:
-- function callback(total_len, received_len, userdata)
-- -- total_len:number类型,body数据总长度
-- -- received_len:number类型,已经下载过的body数据长度
-- -- userdata:下载回调函数使用的用户自定义的回调参数,
-- 即opts.userdata
-- log.info("callback", total_len, received_len, userdata)
-- end
-- 数据类型:function或者nil;
-- 取值范围:无特别限制;
-- 是否必选:可选传入此参数;
-- 注意事项:回调函数是在task之外的业务逻辑中被执行的,在回调函数内部无法使用
-- sys.wait(timeout)、sys.waitUntil(msg, timeout)、
-- sys.waitMsg(task_name, msg, timeout)等必须用在task中的函数;
-- 参数示例:如下所示,定义了一个函数http_download_cbfunc,
-- http_download_cbfunc就可以做为此参数传入;
-- function http_download_cbfunc(total_len, received_len, userdata)
-- log.info("http_download_cbfunc",
-- total_len, received_len, userdata)
-- end
callback = ,
-- 参数含义:回调函数callback使用的回调参数,
-- 自动执行回调函数callback(total_len, received_len, userdata)时,
-- 做为第三个参数使用;
-- 数据类型:任意数据类型;
-- 取值范围:无特别限制;
-- 是否必选:可选传入此参数;
-- 注意事项:暂无;
-- 参数示例:"download_mp3"或者12000或者任意自定义的参数;
userdata = ,
}
数据类型:table或者nil;
取值范围:参考参数含义内各字段说明
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:超时3秒;接收的body数据保存到"/http_download/logo.jpg"中;
接收数据过程中,回调函数为http_cbfunc,回调函数的回调参数为"download_logo":
{
timeout = 3000,
dst = "/http_download/logo.jpg",
callback = http_cbfunc,
userdata = "download_logo"
}
server_ca_cert
参数含义:服务器ca证书数据;
数据类型:string或者nil;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:当客户端需要验证服务器证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给server_ca_cert;
参数示例:例如通过Luatools烧录了server_ca.crt文件,就可以通过io.readFile("/luadb/server_ca.crt")读出文件内容赋值给赋值给server_ca_cert;
client_cert
参数含义:客户端证书数据;
数据类型:string或者nil;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:当服务器需要验证客户端证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给client_cert;
参数示例:例如通过Luatools烧录了clinet.crt文件,就可以通过io.readFile("/luadb/clinet.crt")读出文件内容赋值给赋值给client_cert;
client_key
参数含义:加密后的客户端私钥数据;
数据类型:string或者nil;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:当服务器需要验证客户端证书时,需要此参数,如果加密后的私钥数据在一个文件中,要把文件内容读出来,赋值给client_key;
参数示例:例如通过Luatools烧录了clinet.key文件,就可以通过io.readFile("/luadb/clinet.key")读出文件内容赋值给client.key;
client_password
参数含义:客户端私钥口令数据;
数据类型:string或者nil;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:当服务器需要验证客户端证书时,需要此参数,如果加密后的私钥数据在一个文件中,要把文件内容读出来,赋值给client_password;
参数示例:例如通过Luatools烧录了clinet.password文件,就可以通过io.readFile("/luadb/clinet.password")读出文件内容赋值给client_password;
返回值
local code, headers, body = http.request().wait()
code
含义说明:HTTP请求的执行结果;
数据类型:number;
取值范围:大于等于100或者小于0;
大于等于100时,表示服务器返回的HTTP状态码,例如200表示成功,
详细说明可以通过搜索引擎搜索“HTTP状态码”自行了解;
小于0时,表示内核固件中检测到通信异常,有如下几种
-1:HTTP_ERROR_STATE,错误的状态, 一般是底层异常,如果出现此问题,
可以联系合宙技术人员报issue解决;
-2:HTTP_ERROR_HEADER,错误的响应头部, 通常是服务器问题;
-3:HTTP_ERROR_BODY,错误的响应体,通常是服务器问题;
-4:HTTP_ERROR_CONNECT,连接服务器失败, 未联网,地址错误,域名错误等问题;
-5:HTTP_ERROR_CLOSE,提前断开了连接, 网络或服务器问题;
-6:HTTP_ERROR_RX,接收数据报错, 网络问题;
-7:HTTP_ERROR_DOWNLOAD,下载文件过程报错, 网络问题或下载路径问题;
-8:HTTP_ERROR_TIMEOU, 超时, 包括连接超时,发送数据超时,接收数据超时;
-9:HTTP_ERROR_FOTA,使用此接口下载升级包时,fota功能报错,通常是更新包不合法;
注意事项:暂无;
返回示例:例如200就表示请求成功;
headers
含义说明:HTTP服务器返回的应答头;
数据类型:table或者nil;
取值范围:大于等于100或者小于0;
当code大于等于100时,headers是table类型,表示服务器返回的应答头;
当code小于0时,headers为nil
注意事项:暂无;
返回示例:{
["Content-Length"] = "265",
["Date"] = "Sat, 30 Aug 2025 01",
["Connection"] = "close",
["Content-Type"] = "application/json"
}
body
含义说明:HTTP服务器返回的应答体;
数据类型:string或者number或者nil;
取值范围:当code的返回值大于等于100时,如果请求的body数据不需要保存到文件中,而是直接保存到内存中,
则body表示请求到的数据内容,string类型;
当code的返回值大于等于100时,如果请求的body数据需要保存到文件中,
则body表示保存请求数据后的文件的大小,number类型;
当code的返回值小于0时,body为nil
注意事项:暂无;
返回示例:"this is a text"
6.2 httpplus扩展库
httpplus.request(opts)
功能
http 客户端,发送http请求到服务器,并且接收服务器应答;
只能在task中使用,会阻塞当前task,等待整个过程成功结束或者出现错误异常结束或者超时结束,超时时长和opts.timeout有
参数
opts
参数含义:HTTP请求的参数配置;参数为table类型,table内容格式说明如下:
{
-- 参数含义:HTTP请求的URL地址;
-- 数据类型:string;
-- 取值范围:支持HTTP、HTTPS,支持域名、IP地址,支持自定义端口,
-- 标准的HTTP URL格式都支持;
-- 是否必选:必须传入此参数;
-- 注意事项:暂无;
-- 参数示例:"https://www.luatos.com/"
url = ,
-- 参数含义:HTTP请求方法;
-- 数据类型:string;
-- 取值范围:支持"GET"、"POST"、"HEAD"等所有HTTP请求方法,请求方法用大写字母表示;
-- 是否必选:可选传入此参数;如果没有传入此参数或者传入了nil类型,则使用默认值,
-- 默认值分为以下两种情况:
-- 如果没有设置files,forms,body,bodyfile参数,则默认为"GET"
-- 如果至少设置了files,forms,body,bodyfile中的一种参数,则默认为"POST"
-- 注意事项:暂无;
-- 参数示例:GET请求时填"GET",POST请求时填"POST";
method = ,
-- 参数含义:HTTP请求头列表,键值对的形式;
-- 数据类型:table或者nil;
-- 取值范围:当为table数据类型时,请求头列表中支持一个或者多个请求头;
-- 是否必选:可选传入此参数;
-- 注意事项:暂无;
-- 参数示例:{
-- ["Content-Type"] = "application/x-www-form-urlencoded",
-- ["self_defined_key"] = "self_defined_value"
-- }
headers = ,
-- 参数含义:HTTP POST上传的文件列表,键值对的形式;
-- 数据类型:table或者nil;
-- 取值范围:当为table数据类型时,文件列表中支持一个或者多个文件;
-- 是否必选:可选传入此参数;
-- 注意事项:若存在本参数,会自动强制以multipart/form-data形式上传;
-- 自动支持大文件的快速上传,详细说明参考下文的upload_file_buff参数说明;
-- 参数示例:{
-- ["uploadFile"] = "/luadb/logo.jpg",
-- ["logo1.jpg"] = "/luadb/logo.jpg",
-- }
files = ,
-- 参数含义:HTTP POST上传的表单参数列表,键值对的形式;
-- 数据类型:table或者nil;
-- 取值范围:当为table数据类型时,表单参数列表中支持一个或者多个参数;
-- 是否必选:可选传入此参数;
-- 注意事项:1、若存在本参数并且不存在files参数,会自动强制以
-- application/x-www-form-urlencoded形式上传;
-- 2、若存在本参数并且存在files参数,会自动强制以
-- multipart/form-data形式上传,也就是说支持同时上传文件和表单参数;
-- 参数示例:{
-- ["username"] = "LuatOS",
-- ["password"] = "123456",
-- }
forms = ,
-- 参数含义:HTTP请求体;
-- 数据类型:string或者zbuff或者nil;
-- 取值范围:无特别限制;
-- 是否必选:可选传入此参数;
-- 注意事项:不能与files或者forms同时存在;
-- 参数示例:"123456" 或者 一个zbuff对象 或者 nil
body = ,
-- 参数含义:要上传的一个文件的完整路径;
-- 数据类型:string或者nil;
-- 取值范围:无特别限制;
-- 是否必选:可选传入此参数;
-- 注意事项:会自动读取文件中的内容进行上传;
-- 自动支持大文件的快速上传,详细说明参考下文的upload_file_buff参数说明;
-- 不能与files或者forms同时存在;
-- 可以与body同时存在,与body同时存在时, 优先级高于body参数,也就是说,
-- bodyfile对应的文件路径中的内容在body参数对应的内容之前;
-- 参数示例:"/luadb/logo.jpg"
bodyfile = ,
-- 参数含义:是否打开debug调试信息日志的开关;
-- 数据类型:boolean或者nil;
-- 取值范围:boolean类型时,true表示打开,false表示关闭;
-- 是否必选:可选传入此参数,默认值为false;
-- 注意事项:暂无;
-- 参数示例:false;
debug = ,
-- 参数含义:HTTP请求过程中是否优先尝试ipv6;
-- 数据类型:boolean或者nil;
-- 取值范围:boolean类型时,true表示优先尝试ipv6;false表示不尝试ipv6,直接使用ipv4;
-- 是否必选:可选传入此参数,默认值为false;
-- 注意事项:如果优先尝试ipv6,ipv6失败后,会自动再尝试ipv4;
-- 参数示例:false;
try_ipv6 = ,
-- 参数含义:从发送请求到收到服务器响应,整个过程的超时时长,单位秒;
-- 数据类型:number或者nil;
-- 取值范围:number类型时,取值范围为大于等于0的整数,0表示永久等待;
-- 是否必选:可选传入此参数,默认值为30秒;
-- 注意事项:暂无;
-- 参数示例:20表示超时时长20秒;
timeout = ,
-- 参数含义:上网使用的网卡ID;
-- 数据类型:number或者nil;
-- 取值范围:number类型时,取值范围参考socket api中的常量详解;
-- 是否必选:可选传入此参数;
-- 注意事项:如果没有传入此参数,内核固件会自动选择当前时间点其他功能模块设置的默认网卡;
-- 除非你HTTP请求时,一定要使用某一种网卡,才设置此参数;
-- 如果没什么特别要求,不要设置此参数,使用系统中设置的默认网卡即可 ;
-- 一般来说,LuatOS的网络应用demo中都会有netdrv_device功能模块设置默认网卡;
-- 所以建议使用http.request接口时,不要设置此参数,
-- 直接使用netdrv_device设置的默认网卡就行;
-- 参数示例:socket.LWIP_GP表示使用4G网卡;
adapter = ,
-- 参数含义:上传文件时使用的自定义zbuff缓冲区;
-- 如果在files或者bodyfile中配置了要上传的文件路径,在httpplus扩展库的内部,
-- 对文件的处理逻辑为:
-- 使用一个zbuff缓冲区,每次从文件中最多读取zbuff缓冲区大小的内容,
-- 分包上传给服务器;
-- zbuff缓冲区,按照以下优先级进行选择:
-- 1、如果此处配置了upload_file_buff,则直接使用upload_file_buff缓冲区;
-- 2、如果此处没有配置upload_file_buff,则httpplus扩展库内部,
-- 会根据合宙产品型号信息,自适应创建合适大小的缓冲区:
-- (1) Air8000系列产品、Air780EPM/EHM/EHV/EGH产品,
-- 会自动创建一个128KB的zbuff缓冲区;
-- (2) 其余产品,会自动创建一个24KB的zbuff缓冲区;
-- 数据类型:zbuff或者nil;
-- 取值范围:zbuff类型时,zbuff缓冲区的大小和当前时刻系统可以ram有关,
-- 只要不超过当前可用ram大小,并且使用zbuff.create可以成功创建就行;
-- 是否必选:可选传入此参数;
-- 注意事项:最简单的方式是不需要传入此参数,使用httpplus内部自适应的zbuff就行;
-- 如果httpplus内部自适应的zbuff自适应的zbuff满足不了需求,
-- 在此处再配置upload_file_buff,使用自定义的zbuff缓冲区空间;
-- 最终使用的zbuff缓冲区越大,文件上传的速度理论上就越快;
-- 例如在4G网络环境,上传一个300MB的文件,测试的文件上传速度如下(可供参考):
-- 使用httpplus内部自适应的24KB zbuff缓冲区时,每秒可以上传80多KB;
-- 使用httpplus内部自适应的128KB zbuff缓冲区时,每秒可以上传300多KB;
-- 参数示例:不使用此参数;或者传入一个自定义的64KB的zbuff缓冲区 zbuff.create(1024*64)
upload_file_buff = ,
}
数据类型:table或者nil;
取值范围:参考参数含义内各字段说明
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:url为"http://httpbin.air32.cn/get",超时3秒:
{
url="http://httpbin.air32.cn/get",
timeout=3
}
method为"POST";url为"http://httpbin.air32.cn/post",
自定义请求头为{["my_key"] = "my_value"},
请求body为"This is a raw text message from LuatOS device":
{
method = "POST",
url = "http://httpbin.air32.cn/post",
headers = {["Content-Type"] = "text/plain"},
body = "This is a raw text message from LuatOS device"
}
method为"POST";url为"http://airtest.openluat.com:2900/uploadFileToStatic",
要上传的tf卡中的30MB的文件路径为"/sd/30M_test.txt",
使用httpplus默认的大文件分包上传缓冲区:
{
url = "http://airtest.openluat.com:2900/uploadFileToStatic",
files =
{
["uploadFile"] = "/sd/30M_test.txt",
},
}
返回值
local code, response = httpplus.request(opts)
code
含义说明:HTTP请求的执行结果;
数据类型:number;
取值范围:大于等于100或者小于0;
大于等于100时,表示服务器返回的HTTP状态码,例如200表示成功,
详细说明可以通过搜索引擎搜索“HTTP状态码”自行了解;
小于0时,表示内核固件中检测到通信异常,有如下几种
-1:HTTP_ERROR_STATE,错误的状态, 一般是底层异常,如果出现此问题,可以联系合宙技术人员报issue解决;
-2:HTTP_ERROR_HEADER,错误的响应头部, 通常是服务器问题;
-3:HTTP_ERROR_BODY,错误的响应体,通常是服务器问题;
-4:HTTP_ERROR_CONNECT,连接服务器失败, 未联网,地址错误,域名错误等问题;
-5:HTTP_ERROR_CLOSE,提前断开了连接, 网络或服务器问题;
-6:HTTP_ERROR_RX,接收数据报错, 网络问题;
-7:HTTP_ERROR_DOWNLOAD,下载文件过程报错, 网络问题或下载路径问题;
-8:HTTP_ERROR_TIMEOU, 超时, 包括连接超时,发送数据超时,接收数据超时;
-9:HTTP_ERROR_FOTA,使用此接口下载升级包时,fota功能报错,通常是更新包不合法;
注意事项:暂无;
返回示例:例如200就表示请求成功;
response
含义说明:HTTP请求的应答数据;参数为table类型时,table内容格式说明如下:
{
-- 含义说明:HTTP应答头列表,键值对的形式;
-- 数据类型:table;
-- 取值范围:应答头列表中支持一个或者多个应答头;
-- 注意事项:暂无;
-- 返回示例:{
-- ["Content-Length"] = "265",
-- ["Date"] = "Sat, 30 Aug 2025 01",
-- ["Connection"] = "close",
-- ["Content-Type"] = "application/json"
-- }
headers = ,
-- 参数含义:HTTP应答体;
-- 数据类型:zbuff;
-- 取值范围:无特别限制;
-- 注意事项:通过zbuff的query函数,可以转化为string类型:response.body:query();
-- 也可以通过uart.tx等支持zbuff的函数直接使用,例如uart.tx(1, response.body);
-- 返回示例:一个zbuff对象;
body = ,
}
数据类型:table或者nil;
取值范围:当code的返回值大于等于100时,response表示请求到的数据内容,table类型,具体内容参考含义说明;
当code的返回值小于0时,response为nil
注意事项:暂无;
返回示例:超时3秒;接收的body数据保存到"/http_download/logo.jpg"中;
接收数据过程中,回调函数为http_cbfunc,回调函数的回调参数为"download_logo":
{
headers =
{
["Content-Length"] = "265",
["Date"] = "Sat, 30 Aug 2025 01",
["Connection"] = "close",
["Content-Type"] = "application/json"
},
body = 此处是一个zbuff对象
}
6.3 http核心库和httpplus扩展库的区别
6.3.1 总体区别
http核心库和httpplus扩展库的区别如下:
| 区别项 | http核心库 | httpplus扩展库 |
|---|---|---|
| 文件上传 | 文件最大64KB | 只要内存够用,文件大小不限,上传时扩展库内部自动划分为多个24KB的数据包上传 |
| 文件下载 | 支持,只要文件系统空间够用,文件大小不限 | 不支持 |
| http header的key:value的限制 | 所有header的value总长度不能超过4KB, 单个header的value长度不能超过1KB | 只要内存够用,header长度不限 |
| 鉴权URL自动识别 | 不支持 | 支持 |
| 接收到的body数据存储支持zbuff | 不支持 | 支持,可以直接传输给uart等库,适用于通过http接收到大量数据后,需要传递给其他供模块快速处理,例如通过uart发送给串口外设,对uart传输速率要求比较高 |
| 接收到的body数据存储到内存中 | 最大支持32KB | 只要内存够用,大小不限 |
| chunk编码 | 支持 | 不支持 |
6.3.2 鉴权URL
鉴权URL是指:在URL中包含用户信息,如下图所示:

6.3.3 chunk编码
HTTP 的 Chunk 编码(分块编码)是一种 HTTP 传输机制,核心作用是让服务器在不知道响应体总长度的情况下,就能逐步向客户端发送数据,无需等待所有数据生成完毕。
它解决了传统传输(需通过Content-Length头指定总长度)的局限性,尤其适合动态生成数据(如实时日志、大文件流式传输)的场景。
HTTP/1.1 200 OK
Server: Nginx/1.20.1
Date: Wed, 29 Oct 2025 12:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 58
{"code":0,"msg":"登录成功","data":{"username":"testuser","token":"abc789"}}
6.3.3.1 核心原理
传统 HTTP 响应中,服务器需先计算出响应体的总字节数,再通过Content-Length头告知客户端,客户端据此判断数据是否接收完整。而 Chunk 编码则将响应体拆分为多个 “数据块(Chunk)”,每个块包含 “块大小 + 块数据”,最后一个块用一个 “大小为 0 的块” 标识传输结束。整个过程无需提前知道总长度,生成一块传一块。
6.3.3.2 传输格式
使用 Chunk 编码时,服务器会在响应头中添加Transfer-Encoding: chunked(替代Content-Length),随后的响应体严格遵循以下格式,每个部分以CRLF(\r\n)分隔:
除结尾块之外,其余块数据结构:
| 组成部分 | 格式说明 | 示例值 |
|---|---|---|
| 1. 块大小 | 表示当前数据块的字节数(不含自身和后续CRLF) | 0x31 0x30(表示块数据的长度为0x10字节,即16字节) |
| 2. 块扩展(可选) | 以;开头,附加块的元信息(如块编号、校验值),极少使用 | ;chunk-id=1 |
| 3. CRLF | 分隔 “块大小” 与 “块数据” 的强制换行 | \r\n |
| 4. 块数据 | 实际的二进制 / 文本数据,长度等于 “块大小” 指定的值 | Hello World(10 字节) |
| 5. CRLF | 分隔 “块数据” 与下一个块(或结束块)的强制换行 | \r\n |
例如第一个块包含16字节的数据:
31 30 0D 0A 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 0D 0A
结尾块数据结构为:
| 组成部分 | 格式说明 | 示例值 |
|---|---|---|
| 1. 块大小 | 表示当前数据块的字节数(不含自身和后续CRLF) | 0x30(表示块数据的长度为0x00字节,即0字节) |
| 2. 块扩展(可选) | 以;开头,附加块的元信息(如块编号、校验值),极少使用 | ;chunk-id=1 |
| 3. CRLF | 分隔 “块大小” 与 “块数据” 的强制换行 | \r\n |
| 4. 块数据 | 实际的二进制 / 文本数据,长度等于 “块大小” 指定的值 | 没有这个字段 |
| 5. CRLF | 分隔 “块数据” 与下一个块(或结束块)的强制换行 | \r\n |
| 6. 尾部(可选) | 结束块后可附加多个 “键:值” 格式的元数据(如校验信息),最后以CRLF收尾 | Content-MD5: abc123\r\n |
例如最后一个块:
30 0D 0A 0D 0A
6.3.3.3 响应示例
接下来我使用LuatOS模拟器访问:http://www.air32.cn/,这是一个可以返回chunk编码应答体的测试服务器;
luatos --llt=H:\Luatools\project\Air8000-http.ini
抓取了完整的http应答报文,我们来实际分析一下这种应答格式,加深一下对chunk编码的理解;


接下来我实际操作演示一下这个过程;
6.3.4 如何选择http核心库和httpplus扩展库
http和httpplus如何选择,遵循以下原则:
- http和httpplus两个库如果都能满足需求,可以使用任何一个库接口来开发;
- http和httpplus两个库如果仅有一个库可以满足需求,使用这个库接口来开发;
- http和httpplus两个库如果都不能满足需求,联系合宙技术人员,对新功能进行支持;
第七部分:LuatOS上的HTTP client 应用示例
在理解了http核心库和httpplus扩展库之后,我们再来看一些LuatOS上的HTTP应用示例demo项目代码,重点分析下如何在项目中使用LuatOS http核心库和httpplus扩展库;
7.1 http demo项目
独立的http应用demo项目代码路径:Air8000 http demo ;
http+tf卡,演示大文件上传和下载的项目代码路径:Air8000 tf_card demo ;
7.2 模拟器上运行 独立的http应用demo(使用模拟器单网卡演示项目完整的业务逻辑)
首先我们在LuatOS模拟器上运行一下这个独立的http应用demo,让大家对实现的功能有一个直观的认识,关于模拟器的使用参考:LuatOS模拟器使用说明
如果要在LuatOS模拟器上运行这个项目,按照以下几点准备好软件环境:
- netdrv_device.lua中打开pc模拟器的网卡驱动文件require "netdrv_pc",注释掉其他其他网卡文件;
软件环境准备好之后,接下来我们在模拟器上实际运行一下这个项目看看效果;
双击 cmd 命令行窗口,然后输入下面一行命令,运行 luatos 批处理文件,同时输入要运行的 luatos 项目配置文件
luatos --llt=H:\Luatools\project\Air8000-http.ini
然后按回车键,就可以运行 http应用demo;
因为模拟器还不支持内部Flash文件系统的一些功能,所以在模拟器上演示下载文件时会失败,这一部分功能,我们放到Air8000开发板上去运行演示;
这个独立的http应用demo中的readme文件,以及代码中的注释都比较详细,接下来我用vscode直接打开这份demo项目,和大家一起分析下代码;
7.3 Air8000开发板上运行演示这两个项目
独立的http应用demo项目代码路径:Air8000 http demo ;
http+tf卡,演示大文件上传和下载的项目代码路径:Air8000 tf_card demo ;
下面我们在Air8000开发板运行演示一下这两个项目;
7.3.1 独立的http应用demo
准备硬件环境:
1、Air8000开发板一块+可上网的sim卡一张+4g天线一根+wifi天线一根+网线一根:
-
sim卡插入开发板的sim卡槽
-
天线装到开发板上
-
网线一端插入开发板网口,另外一端连接可以上外网的路由器网口或者交换机网口;
2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air8000开发板和数据线的硬件接线方式为:
-
Air8000开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
-
TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
准备软件环境:
- 手机打开WiFi热点zhutianhua 123qweasd,修改netdrv_multiple.lua中的WiFi信息;
- netdrv_device.lua打开多网卡驱动单独演示;
- Luatools烧录内核固件以及修改后的demo脚本;
多网卡演示:
开机后,在Luatools运行日志中搜索 new adapter,如下图所示,按照红色文字操作演示

http业务演示:
直接运行并且分析日志;
搜索success,每一轮完整的请求,一共出现22次success,则表示全部业务执行成功(http_app.lua中使用http核心库执行13次http请求,httpplus_app.lua中使用httpplus扩展库执行9次http请求)

完整的指南文章参考:Air8000 HTTP应用教程;
7.3.2 http下载上传文件+tf卡 应用demo
准备硬件环境:
1、Air8000开发板一块+可上网的sim卡一张+4g天线一根+tf卡一张:
-
sim卡插入开发板的sim卡槽
-
天线装到开发板上
-
tf卡插入开发板的tf卡槽
2、 TYPE-C USB数据线一根 + USB转串口数据线一根,Air8000开发板和数据线的硬件接线方式为:
-
Air8000开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
-
TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
准备软件环境:
1. main.lua中分别单独打开require "http_download_file" 或者 require "http_upload_file";先打开require "http_download_file",烧录软件,下载一个3.23MB的文件到tf卡中;然后再单独打开require "http_upload_file",将3.23MB的文件上传到另一个http服务器;
--加载tf卡测试应用模块
-- require "tfcard_app"
--加载HTTP下载存入TF卡功能演示模块
-- require "http_download_file"
--加载HTTP上传文件到服务器的功能演示模块
-- require "http_upload_file"
2. Luatools烧录内核固件以及修改后的demo脚本;
http下载大文件业务演示:
直接运行并且分析日志;

http上传大文件业务演示:
直接运行并且分析日志;

完整的指南文章参考:Air8000 TF卡应用教程;
第八部分:HTTP client常见问题分析
在http开发使用过程中,或多或少,我们都会遇到一些问题,遇到问题时,如果我们自己可以根据日志进行初步分析,可能会大大提高解决问题的效率;在本章节我们提供三种日志分析方法;
8.1 三种日志分析方法
8.1.1 Luatools抓取的应用日志分析
这部分是Luatools抓取的应用日志,在Luatools的主窗口可以实时显示,如下图所示:

应用日志分为两种类型:
- 一部分是Lua脚本中输出的日志,有D/、I/、W/、E/几种前缀,这部分日志,大家根据自己编写的Lua脚本逻辑自行分析即可;
- 另一部分是内核固件中输出的一些关键日志,没有D/、I/、W/、E/几种前缀,这部分日志,大家不知道所表示的意义,我们会在本章节的以下内容中针对一些常见的日志进行逐一分析;
8.1.2 抓取LuatOS模拟器上的网络交互包进行分析
http应用完全可以在LuatOS模拟器上运行,所以我们可以使用LuatOS模拟器来运行自己的程序;
在运行过程中使用wireshark抓取网络交互包,进行详细分析,例如下图是在LuatOS模拟器运行过程中,tls+单向认证连接服务器失败的网络交互包,从交互包中可以准确的得知,失败原因是Unknown CA,未识别的CA证书

这种分析方法对于解决socket,http,mqtt,ftp等网络应用的问题,帮助很大!
8.1.3 Luatools抓取的底层日志网络交互包分析
这里所说的抓取底层日志网络交互包分析,和 8.1.2 抓取LuatOS模拟器上的网络交互包进行分析 相比,一个是在真实的硬件板上(例如Air8000)运行抓日志,一个是在LuatOS模拟器上运行抓网络交互包;
在真实的硬件板上抓到日志后,然后再使用底层日志分析工具提取出来网络交互包,然后再使用wireshark对提取出来的网络交互包进行分析;
和模拟器运行直接抓网络交互包相比,这种方式有以下几种明显的缺点:
1、抓取日志以及分析操作繁琐;
2、硬件板内存资源有限,抓到的网络日志会丢包,特别是网络数据交互频繁的时候,丢包问题严重;
虽然有以上两项缺点,但是因为是真实的运行环境,对于LuatOS模拟器运行无法分析解决的问题,最终还是要通过这种方式解决;
如果需要用到这种方式来分析解决问题,当前阶段,客户只需要提交Luatools抓到的日志给合宙技术人员即可,由合宙技术人员进行分析;
后续我们也会在docs.openluat.com写一篇文章,单独讲解如何使用这种方法分析问题;大家如果有兴趣,可自行查看文档学习。
所以,在本章节接下来的内容中,我会针对一些常见的问题,分别使用Luatools应用日志分析 和LuatOS模拟器上的网络交互包分析 这两种方法来讲述;
这些业务逻辑的异常情况,就不再一一分析了,大家遇到问题后,可以参考上面几种异常的分析方法,自行分析;
8.2 http连接过程(dns+tcp+tls)中的问题
8.2.1 dns解析失败
GET请求访问一个URL(这个URL的域名错误):http://www.air3200.cn/
8.2.1.1 Luatools应用日志分析
如下图所示,解析域名:www.air3200.cn;
一共四个DNS服务器,每个服务器尝试解析3次,最终没有解析成功,提示:dns_run 649:no ipv6, no ipv4

8.2.1.2 LuatOS模拟器上的网络交互包分析

8.2.1.3 解决方案
出现此种错误,可以通过以下几步尝试分析解决:
1、可以在电脑浏览器中输入完整的URL,对比确认下域名输入是否正确;如果电脑浏览器也提示DNS出错,如下图所示,大概率是域名本身出错了;

2、如果第一步电脑浏览器可以访问,则大概率是LuatOS中运营商默认的DNS服务器出了问题,此时可以参考http demo中netdrv目录下每种网卡的驱动文件代码,使用socket.setDNS(adapter_id, dns_index, ip)配置自定义的DNS服务器;
以4G网卡为例,参考netdrv_4g.lua中的下图代码

8.2.2 tcp握手连接失败
8.2.2.1 Luatools应用日志分析
当对端主机存在,端口不存在时,例如:访问 http://www.air32.cn:1024/,主机为:www.air32.cn,端口1024(不存在)
会有以下异常日志: net_lwip_tcp_err_cb 662:adapter 1 socket 2 not closing, but error -13

这里有一个错误值:-13,表示:
ERR_ABRT = -13
- 含义:连接被中止。
- 场景:TCP 连接被本地或对端异常中止(如收到 RST 包,或本地主动调用
tcp_abort)。
具体到本日志,因为对端端口不存在,所以应该是tcp三次握手过程中,超时,LuatOS内核固件主动断开了连接;
还有一种常见的错误是对端主机不存在,此时在异常日志中的错误值很可能是:-13;
如下图所示,访问 http://113.126.89.86/,主机ip地址为:113.126.89.86(不存在)
net_lwip_tcp_err_cb 662:adapter 1 socket 0 not closing, but error -13

这里有一个错误值:-13,表示:
ERR_ABRT = -13
- 含义:连接被中止。
- 场景:TCP 连接被本地或对端异常中止(如收到 RST 包,或本地主动调用
tcp_abort)。
具体到本日志,因为对端ip不存在,所以应该是tcp三次握手过程中,超时,内核固件主动断开了连接;
在LuatOS内核固件中,使用的是LwIP协议栈,此处的错误值的完整的取值范围如下所述(大家在平时开发过程中,如果遇到异常,根据日志中的错误值,可以参考这部分说明自行简单分析下是什么原因):
1、ERR_OK = 0:无错误,操作成功;
2、ERR_MEM = -1:内存分配失败;
3、ERR_BUF = -2:
-
含义:缓冲区错误(不足或大小不匹配)。
-
场景:发送数据时缓冲区空间不足(如
pbuf大小不够存放待发送数据),或接收时缓冲区溢出。
4、ERR_TIMEOUT = -3
-
含义:操作超时。
-
场景:TCP 连接超时(未收到 SYN-ACK)、ARP 请求超时(未收到目标 MAC 地址应答)、
netconn_recv等待数据超时等。
5、ERR_RTE = -4
-
含义:路由错误(无可用路由)。
-
场景:发送 IP 数据包时,lwip 路由表中找不到目标 IP 地址的有效路由(如未配置默认网关且无直连路由)。
6、ERR_INPROGRESS = -5
-
含义:操作正在进行中。
-
场景:非阻塞模式下的操作(如
tcp_connect)尚未完成,需等待后续回调通知结果。
7、ERR_VAL = -6
-
含义:无效值(参数值非法)。
-
场景:传入 API 的参数值超出合法范围(如端口号为 0 或大于 65535,IP 地址格式错误等)。
8、ERR_WOULDBLOCK = -7
-
含义:操作会阻塞(非阻塞模式下)。
-
场景:非阻塞模式下调用
netconn_recv时暂无数据可接收,或tcp_send时发送窗口未满导致无法立即发送。
9、ERR_USE = -8
-
含义:地址已被使用。
-
场景:绑定端口时(
udp_bind、tcp_bind),指定的端口已被其他连接占用。
10、ERR_ALREADY = -9
-
含义:已在连接中。
-
场景:对已处于连接过程中的 TCP 控制块再次调用
tcp_connect。
11、ERR_ISCONN = -10
-
含义:连接已建立。
-
场景:对已连接的 TCP 连接再次调用
tcp_connect,或对已连接的netconn执行不需要连接的操作(如bind)。
12、ERR_CONN = -11
-
含义:未连接状态。
-
场景:对未建立连接的
netconn调用send,或关闭未连接的连接。
13、ERR_IF = -12
-
含义:底层网络接口(netif)错误。
-
场景:网络接口未初始化、链路断开(如以太网物理层未连接),导致数据包无法发送。
14、ERR_ABRT = -13
-
含义:连接被中止。
-
场景:TCP 连接被本地或对端异常中止(如收到 RST 包,或本地主动调用
tcp_abort)。
15、ERR_RST = -14
-
含义:连接被重置。
-
场景:TCP 连接收到对端发送的 RST(重置)报文,导致连接强制关闭。
16、ERR_CLSD = -15
-
含义:连接已关闭。
-
场景:对已正常关闭的连接执行读写操作(如
tcp_send在连接关闭后调用)。
17、ERR_ARG = -16
-
含义:非法参数(参数类型或指针无效)。
-
场景:传入 API 的参数为
NULL(如netconn_new传入无效的协议类型,tcp_send传入NULL缓冲区)。
18、ERR_IF_HIGH_WATER = -17
-
含义:底层网络接口达到高水位线(缓冲区满)。
-
场景:网络接口发送缓冲区已满,暂时无法接收新的发送请求(通常用于流量控制)。
19、ERR_IF_SUSPEND = -18
-
含义:底层网络接口被挂起。
-
场景:网络接口因某种原因(如手动暂停、错误恢复中)暂时不可用。
20ERR_IF_OOS = -19
-
含义:底层网络接口处于 “OutOfService”(服务中断)状态。
-
场景:网络接口硬件故障、驱动错误等导致完全无法提供服务。
8.2.2.2 LuatOS模拟器上的网络交互包分析


8.2.2.3 解决方案
出现此种错误,可以在电脑浏览器中输入完整的URL,对比确认下电脑浏览器是否可以正常访问;
如果电脑浏览器也提示出错,大概率是URL出错了;
如果电脑浏览器可以正常访问,联系合宙技术人员分析解决此问题;
8.2.3 tls握手连接失败
8.2.3.1 Luatools应用日志分析
在tls连接+仅支持单向认证的场景中,如果我们在客户端配置了错误的CA证书,本来应该是baidu_parent_ca.crt文件中的内容,现在配置为了错误的openluat_root_ca.crt文件,如下图所示:

在连接过程中,会有以下异常日志:network_state_shakehand 807:0x2700, 3

这里有一个错误值:0x2700,MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,表示证书验证失败;
在LuatOS内核固件中,使用的是MbedTLS开源库,TLS握手连接过程中,MbedTLS中有以下常见的错误值(大家在平时开发过程中,如果遇到异常,根据日志中的错误值,可以参考这部分说明自行简单分析下是什么原因,如果这里没有覆盖出现的错误值,可以使用AI工具提问,例如错误值为0x2700,可以提问:tls握手连接过程中,0x2700表示什么错误):
1、基础加密 / 解密错误
-
MBEDTLS_ERR_SSL_DECRYPTION_FAILED(0x7080):解密失败(如对称加密密钥错误、密文损坏)。 -
MBEDTLS_ERR_SSL_BAD_HMAC(0x7082):HMAC 验证失败(消息完整性校验不通过,可能被篡改)。 -
MBEDTLS_ERR_SSL_BAD_RECORD_MAC(0x7084):记录层 MAC 校验失败(与 HMAC 类似,针对 TLS 记录的完整性)。
2、协议版本与协商错误
-
MBEDTLS_ERR_SSL_UNSUPPORTED_VERSION(0x7000):不支持对方的 TLS 版本(如客户端要求 TLS 1.0,服务器仅支持 TLS 1.2+)。 -
MBEDTLS_ERR_SSL_VERSION_MISMATCH(0x7002):版本协商不匹配(如客户端和服务器协商的版本不一致)。
3、密码套件与算法错误
-
MBEDTLS_ERR_SSL_NO_SHARED_CIPHER(0x7004):无共同支持的密码套件(客户端与服务器的密码套件列表无交集)。 -
MBEDTLS_ERR_SSL_UNSUPPORTED_CIPHERSUITE(0x7006):对方选择的密码套件本地不支持。 -
MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION(0x7008):不支持对方发送的 TLS 扩展(如 ALPN、SNI 扩展不被认可)。
4、证书验证错误
-
MBEDTLS_ERR_X509_CERT_VERIFY_FAILED(0x2700):证书验证失败(如签名无效、过期、吊销)。 -
MBEDTLS_ERR_X509_UNKNOWN_CA(0x2702):证书链中存在未知 CA(根证书不被信任)。 -
MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED(0x7040):服务器要求客户端证书,但客户端未提供。 -
MBEDTLS_ERR_SSL_BAD_CERTIFICATE(0x7042):证书格式错误或内容无效(如解析失败、字段不合法)。
5、密钥交换与认证错误
-
MBEDTLS_ERR_SSL_KEY_EXCHANGE_FAILED(0x7020):密钥交换过程失败(如 RSA 密钥解密失败、ECDH 密钥协商错误)。 -
MBEDTLS_ERR_SSL_BAD_CLIENT_KEY_EXCHANGE(0x7022):客户端密钥交换消息格式错误或内容无效。 -
MBEDTLS_ERR_SSL_BAD_CERTIFICATE_VERIFY(0x7044):客户端证书验证消息(CertificateVerify)无效(如签名不匹配)。
6、握手流程与消息错误
-
MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE(0x7060):收到意外的握手消息(如流程顺序错误,如先收到 Finished 再收到 Certificate)。 -
MBEDTLS_ERR_SSL_INVALID_HANDSHAKE_MESSAGE(0x7062):握手消息格式无效(如长度错误、字段缺失)。 -
MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE(0x7064):握手失败(通用错误,可能因上述多种原因导致,如服务器拒绝客户端配置)。 -
MBEDTLS_ERR_SSL_HANDSHAKE_TIMEOUT(0x7066):握手超时(未在规定时间内收到对方响应)。
7、连接与状态错误
-
MBEDTLS_ERR_SSL_CONN_EOF(0x70a0):握手过程中连接被关闭(对方发送了关闭通知)。 -
MBEDTLS_ERR_SSL_CONNECTION_RESET(0x70a2):连接被重置(如底层 TCP 连接断开)。 -
MBEDTLS_ERR_SSL_WANT_READ/MBEDTLS_ERR_SSL_WANT_WRITE(0x70c0 / 0x70c2):非阻塞模式下需要继续读写数据(非错误,需重试)。
8.2.3.2 LuatOS模拟器上的网络交互包分析

8.2.3.3 解决方案
出现TLS握手失败这类错误,可以根据Luatools上的日志进行分析,都能定位到具体的原因;
如果定位不到,联系合宙技术人员分析解决;
8.3 http应用层中的问题
在经过8.2步的连接过程之后,如果连接成功,说明上下行的网络已经基本没问题了;
如果使用http.request或者httpplus.request接口,最终还会返回其他类型的错误值;
大部分的错误,都可以采用以下方式解决:
- 使用电脑浏览器,或者postman、curl等工具对比测试,这些工具在网上有很多教程,如果不会使用可以自行学习;
- 如果第1步都不成功,大概率是http服务器的问题;否则的话,就是LuatOS软件的问题,联系合宙技术人员分析解决;
8.4 支持客户过程中遇到的其他问题
8.4.1 三个域名对应同一个IP地址,http编码时要求使用用一个IP地址访问
详细需求为:
- 有三个域名:test1.example.net、test2.example.net、test3.example.net,对应的IP地址都为112.234.222.132;
- 使用http.request编码时,要求直接传入IP地址112.234.222.132,不能使用各自的域名,这样就节省了DNS解析的时间;
- 但是服务器还要知道,http.request请求时,携带的是哪一个域名;
我们先回顾一下4.2.2章节,http请求-响应的过程中的前3步中的核心流程:
- DNS解析,把域名解析为IP地址;因为此客户的需求是不需要DNS解析,已经给出了IP地址,所以我们可以直接跳过这一步;
- TCP连接,TCP连接过程中,只要知道服务器的IP地址和端口即可;IP地址在第1步已经获得;所以此步骤也没有问题;
- 构造HTTP请求报文并且发送给服务器,这是满足客户需求的关键一步;我们看一个上文抓到的HTTP请求报文:
-

-
上图的请求头中,有一个Host字段,
Host: ``httpbin.air32.cn,这里就可以标记域名; -
而http.request()和httpplus.request()接口都可以设置自定义的请求头,或者标准的请求头;
-
Host属于一个标准请求头:
-
如果用户不主动设置,库里面也会自动设置;
-
如果用户主动设置,库里面会使用用户设置的标准头;
- 基于以上分析,用户的需求
-
如果使用http核心库来实现,核心代码片段如下:
-
```Lua http.request("GET", "http://112.234.222.132/", {["Host"] = "test1.example.net"}).wait()
http.request("GET", "http://112.234.222.132/", {["Host"] = "test2.example.net"}).wait()
http.request("GET", "http://112.234.222.132/", {["Host"] = "test3.example.net"}).wait() ```
-
如果使用httpplus扩展库来实现,核心代码片段如下:
-
```Lua httpplus.request({ url = "http://112.234.222.132/", headers = {["Host"] = "test1.example.net"} })
httpplus.request({ url = "http://112.234.222.132/", headers = {["Host"] = "test2.example.net"} })
httpplus.request({ url = "http://112.234.222.132/", headers = {["Host"] = "test3.example.net"} }) ```
8.4.2 下载1MB或者4MB的文件失败
客户使用Air780EPM,没有外挂Flash或者TF卡,使用http核心库接口下载1MB或者4MB的文件失败;
原因是Air780EPM模组内部Flash空间不足,没办法保存1MB或者4MB的文件,所以下载失败;
在docs.openluat.com网站,每个具体产品下都有 固件和demo->固件版本 这一级目录;
以Air780EPM为例,点击Air780EPM LuatOS 固件版本,我们可以看到如下图所示的信息:
可以看到,Air780EPM LuatOS固件一共有5种,每种固件对应的文件系统总大小不完全一样,有168KB和192KB两种,无论是哪一种,都不能满足保存1MB或者4MB文件的需求;

遇到此类问题时,有以下三种解决方案:
- HTTP下载文件时如果不是必须要下载完保存到本地文件,而是可以下载一段处理一段,则可以使用断点续传的方式处理,前提是HTTP服务器要支持断点续传功能;
-
断点续传过程中,下载下来的每段数据可以保存到ram中进行处理,也可以保存到内部Flash的文件系统中进行处理;
-
只要每次请求的数据不要超过ram可用空间或者内部Flash的文件系统可用空间即可;
-
如果下载需要下载一个4MB的文件,每次断点下载16KB,每次下载的16KB,直接保存到内存ram中进行处理;
-
需要下载256次;
-
使用http核心库来实现,核心代码片段如下( 为了说明方便,下面写了很多行代码,实际实现时,可用用循环语句实现):
-
```Lua -- 请求第一块16KB数据 http.request("GET", "https://test.com/4MB.bin", {["Range"] = "0-16383"}).wait()
-- 请求第二块16KB数据 http.request("GET", "https://test.com/4MB.bin", {["Range"] = "16384-32767"}).wait()
-- 请求第三块16KB数据 http.request("GET", "https://test.com/4MB.bin", {["Range"] = "32768-49151"}).wait()
-- 此处省略了252次调用
-- 请求第256块16KB数据(最后一块) -- "4177920-"没有写结束位置,表示请求到末尾,在本示例中,等价于"4177920-4194303" http.request("GET", "https://test.com/4MB.bin", {["Range"] = "4177920-"}).wait() ```
- 外挂Flash或者TF卡进行下载保存;可以参考tf_card的demo,或者lf的demo;
- 更换文件系统空间比较大的模组型号,例如Air780EHM模组的11号或者111号LuatOS固件有3584KB的内置文件系统空间可用;