跳转至

作者:朱天华

Hello,大家好,我是朱天华。

欢迎大家来到合宙LuatOS直播课堂,一起学习LuatOS课程。

第一部分:LuatOS课程背景

因为今天是我们LuatOS系列课程的第四讲,所以在这里我就不重复讲解整个LuatOS课程的背景了;

如果您还不清楚LuatOS课程背景,可以访问:LuatOS课程背景 这个链接,进行了解;

第二部分:LuatOS HTTP课程讲哪些内容

今天的LuatOS HTTP课程主要从以下几方面进行讲述:

首先,复习以下两项内容:

  1. TCP/IP总体介绍;
  2. LuatOS上的 4G/WiFi/以太网 三种网络环境下的TCP/IP协议栈总体介绍;

以上这两部分理论知识的内容和LuatOS课程的第002讲 socket重复,所以在本讲中仅仅对一些核心知识点进行简单的复习回顾,同时重点说明一下这些理论知识和HTTP的关系;

如果你还不清楚这些理论知识内容,可以访问:LuatOS socket(第三部分到第五部分) 这个链接,再学习一下;

其次,从以下几点,重点学习下HTTP的理论知识:

  1. HTTP协议和交互流程分析;
  2. HTTP协议报文;

通过以上两点的学习,让大家对HTTP本身有一个详细的理解;

最后,基于LuatOS上的HTTP能力,从以下几点,重点学习下LuatOS HTTP的应用编程:

  1. LuatOS http核心库httpplus扩展库 功能介绍;
  2. LuatOS HTTP client应用开发示例;
  3. 客户在使用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根据支持的身份认证类型,又可以分为以下三类:

  1. 不支持身份认证的加密HTTPS;
  2. 支持单向身份认证的加密HTTPS,client验证server的身份;
  3. 支持双向身份认证的加密HTTPS,client和server互相验证对方的身份;

再往下看数据链路层,支持以太网,WiFi,4G等常见的数据链路承载,对于HTTP应用来说,并不关心数据链路层的承载是什么类型,只关心有一个可用的数据链路网络就行;

LuatOS软件支持4G,以太网,WiFi三种数据链路承载网络,并且也支持三种网络之间按照配置的优先级无感自动切换。

3.2 HTTP应用数据在TCP/IP数据包中的位置

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

  1. 在HTTP client请求端,HTTP网络协议生成HTTP应用数据
  2. HTTP应用数据向下传,交给传输层,增加了传输层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源端口号目标端口号,这两个端口号的作用是:定义了发送端和接收端的应用程序,例如HTTP服务器,如果提供未加密的服务,则目标端口号一般为80;如果提供加密的服务,则目标端口号一般为443;通过端口号,可以提供两个主机之上端到端的通信;
  3. 传输层头部+HTTP应用数据向下传,交给网络层,增加了网络层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源IP地址目标IP地址,这两个IP地址的作用是:定义了发送端和接收端的网络设备地址;可以提供主机到主机的通信;
  4. 网络层头部+传输层头部+HTTP应用数据向下传,交给数据链路层,增加了数据链路层的头部信息;在这一层会根据不同的数据链路承载(例如4G网络,WiFi网络,以太网),增加不同的数据链路层头部;
  5. 最终,数据链路层头部+网络层头部+传输层头部+HTTP应用数据的数据包,通过物理层转换为0和1的电信号,然后通过物理传输介质发给了HTTP server端;
  6. HTTP server端收到数据后,从下到上,依次解析并且剥离出数据链路层头部、网络层头部、传输层头部,最终将HTTP应用数据交给HTTP应用程序去处理;
  7. 接收端处理完应用数据后,如果需要返回应用数据给发送端;此时发送端和接收端的角色互换,再走一遍数据分层的封装和解封装过程;

接下来我使用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.cnConnection: 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/jsonContent-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 OKHTTP/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应用编程,我们仅需要关注上表中黄色背景的几部分:

  1. http核心库和httpplus扩展库:这两个库是LuatOS HTTP client应用编程的核心,在下一章节我们会重点讲解;
  2. socket核心库、"IP_READY"和"IP_LOSE"消息:在LuatOS HTTP应用开发中,会主动查询网卡的状态,或者被动等待网卡状态的变化通知,这部分功能就会用到socket核心库、"IP_READY"和"IP_LOSE"消息;在本讲最后我们学习LuatOS http client开发示例时,再结合具体的代码演示如何使用这部分功能;
  3. 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
取值范围:支持HTTPHTTPS,支持域名、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=100name=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时,表示内核固件中检测到通信异常,有如下几种
             -1HTTP_ERROR_STATE,错误的状态, 一般是底层异常,如果出现此问题,
                 可以联系合宙技术人员报issue解决    
             -2HTTP_ERROR_HEADER,错误的响应头部, 通常是服务器问题;    
             -3HTTP_ERROR_BODY,错误的响应体,通常是服务器问题;    
             -4HTTP_ERROR_CONNECT,连接服务器失败, 未联网,地址错误,域名错误等问题;    
             -5HTTP_ERROR_CLOSE,提前断开了连接, 网络或服务器问题;    
             -6HTTP_ERROR_RX,接收数据报错, 网络问题;    
             -7HTTP_ERROR_DOWNLOAD,下载文件过程报错, 网络问题或下载路径问题;    
             -8HTTP_ERROR_TIMEOU 超时, 包括连接超时,发送数据超时,接收数据超时;    
             -9HTTP_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时,表示内核固件中检测到通信异常,有如下几种
             -1HTTP_ERROR_STATE,错误的状态, 一般是底层异常,如果出现此问题,可以联系合宙技术人员报issue解决    
             -2HTTP_ERROR_HEADER,错误的响应头部, 通常是服务器问题;    
             -3HTTP_ERROR_BODY,错误的响应体,通常是服务器问题;    
             -4HTTP_ERROR_CONNECT,连接服务器失败, 未联网,地址错误,域名错误等问题;    
             -5HTTP_ERROR_CLOSE,提前断开了连接, 网络或服务器问题;    
             -6HTTP_ERROR_RX,接收数据报错, 网络问题;    
             -7HTTP_ERROR_DOWNLOAD,下载文件过程报错, 网络问题或下载路径问题;    
             -8HTTP_ERROR_TIMEOU 超时, 包括连接超时,发送数据超时,接收数据超时;    
             -9HTTP_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如何选择,遵循以下原则:

  1. http和httpplus两个库如果都能满足需求,可以使用任何一个库接口来开发;
  2. http和httpplus两个库如果仅有一个库可以满足需求,使用这个库接口来开发;
  3. 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模拟器上运行这个项目,按照以下几点准备好软件环境:

  1. 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口;

准备软件环境:

  1. 手机打开WiFi热点zhutianhua 123qweasd,修改netdrv_multiple.lua中的WiFi信息;
  2. netdrv_device.lua打开多网卡驱动单独演示;
  3. 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的主窗口可以实时显示,如下图所示:

应用日志分为两种类型:

  1. 一部分是Lua脚本中输出的日志,有D/、I/、W/、E/几种前缀,这部分日志,大家根据自己编写的Lua脚本逻辑自行分析即可;
  2. 另一部分是内核固件中输出的一些关键日志,没有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_bindtcp_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接口,最终还会返回其他类型的错误值;

大部分的错误,都可以采用以下方式解决:

  1. 使用电脑浏览器,或者postman、curl等工具对比测试,这些工具在网上有很多教程,如果不会使用可以自行学习;
  2. 如果第1步都不成功,大概率是http服务器的问题;否则的话,就是LuatOS软件的问题,联系合宙技术人员分析解决;

8.4 支持客户过程中遇到的其他问题

8.4.1 三个域名对应同一个IP地址,http编码时要求使用用一个IP地址访问

详细需求为:

  1. 有三个域名:test1.example.net、test2.example.net、test3.example.net,对应的IP地址都为112.234.222.132;
  2. 使用http.request编码时,要求直接传入IP地址112.234.222.132,不能使用各自的域名,这样就节省了DNS解析的时间;
  3. 但是服务器还要知道,http.request请求时,携带的是哪一个域名;

我们先回顾一下4.2.2章节,http请求-响应的过程中的前3步中的核心流程:

  1. DNS解析,把域名解析为IP地址;因为此客户的需求是不需要DNS解析,已经给出了IP地址,所以我们可以直接跳过这一步;
  2. TCP连接,TCP连接过程中,只要知道服务器的IP地址和端口即可;IP地址在第1步已经获得;所以此步骤也没有问题;
  3. 构造HTTP请求报文并且发送给服务器,这是满足客户需求的关键一步;我们看一个上文抓到的HTTP请求报文:
  4. 上图的请求头中,有一个Host字段,Host: ``httpbin.air32.cn,这里就可以标记域名;

  5. 而http.request()和httpplus.request()接口都可以设置自定义的请求头,或者标准的请求头;

  6. Host属于一个标准请求头:

  7. 如果用户不主动设置,库里面也会自动设置;

  8. 如果用户主动设置,库里面会使用用户设置的标准头;

  9. 基于以上分析,用户的需求
  10. 如果使用http核心库来实现,核心代码片段如下:

  11. ```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() ```

  12. 如果使用httpplus扩展库来实现,核心代码片段如下:

  13. ```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文件的需求;

遇到此类问题时,有以下三种解决方案:

  1. HTTP下载文件时如果不是必须要下载完保存到本地文件,而是可以下载一段处理一段,则可以使用断点续传的方式处理,前提是HTTP服务器要支持断点续传功能;
  2. 断点续传过程中,下载下来的每段数据可以保存到ram中进行处理,也可以保存到内部Flash的文件系统中进行处理;

  3. 只要每次请求的数据不要超过ram可用空间或者内部Flash的文件系统可用空间即可;

  4. 如果下载需要下载一个4MB的文件,每次断点下载16KB,每次下载的16KB,直接保存到内存ram中进行处理;

  5. 需要下载256次;

  6. 使用http核心库来实现,核心代码片段如下( 为了说明方便,下面写了很多行代码,实际实现时,可用用循环语句实现):

  7. ```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() ```

  8. 外挂Flash或者TF卡进行下载保存;可以参考tf_card的demo,或者lf的demo;
  9. 更换文件系统空间比较大的模组型号,例如Air780EHM模组的11号或者111号LuatOS固件有3584KB的内置文件系统空间可用;