HTTP
一、HTTP 概述
1.1 简介
HTTP 是 Hyper Text Transfer Protocol(超文本传输协议)的缩写.HTTP 是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型.HTTP 是一个无状态的协议.HTTP 协议通常承载于 TCP 协议之上,有时也承载于 TLS 或 SSL 协议层之上,这个时候,就成了我们常说的 HTTPS,所以 HTTPS 相关的指令只需要参考 SSL 部分配置连接,其他和 http 都是一样的.
HTTP 协议的_主要应用场景_有:基于浏览器的网页获取与表单提交、文件上传与下载、移动应用、物联网设备的数据上报等.
1.2 请求报文
1. method:请求方法,GET 和 POST 是最常见的 HTTP 方法. 2. URL:为请求对应的 URL 地址,它和报文头的 Host 属性组成完整的请求 URL. 3. Version:协议名称及版本号. 4. Header lines:HTTP 的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息. 5. Entity body:是报文体,它将一个页面表单中的组件值通过 param1=value1&m2=value2 的键值对形式编码成一个格式化串,它承载多个请求参数的数据. 6. 请求报文示例如下:
1.3 响应报文
1. version:报文协议及版本. 2. status code:状态码及状态描述. 3. phrase:原因短语. 4. Header lines:响应报文头. 5. Entity body:响应报文体,即我们真正要的内容. 6. 响应报文示例如下:
注意:sp 表示空格,cr lf 表示回车换行,报文头和报文体之间要有一行空格
1.4 HTTP 请求方法
HTTP 客户端发出请求,告知服务端需要执行不同类型的请求命令,这些命令被称为 HTTP 方法.
1. GET:获取资源方法 2. POST:传输实体数据方法 3. HEAD:获取头部报文方法 4. PUT:传输文件方法 5. DELETE:删除指定资源方法
1.5 HTTP 状态码
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态 码的类型.响应分为五类:
1. 信息响应(100–199),信息响应中,服务器收到请求,需要请求者继续执行操作 2. 成功响应(200–299),信息响应成功,操作被成功接收并处理 3. 重定向,需要进一步操作(300–399),信息需要被重新定向,需要进一步的操作以完成请求 4. 客户端错误(400–499),客户端错误,请求包含语法错误或无法完成请求 5. 服务器错误(500–599),服务器错误,服务器在处理请求的过程中发生了错误
二、演示功能概述
本文教你合宙 4G 模组使用 LuatOS 开发 4G 通信中 http 网络协议的应用,实现模组和服务器之间数据的传输.
本教程实现的功能定义是:
使用 Air780EP 核心板下载 Air780 的 LuatOS 示例代码中 http 的例程进行验证,包含 get 请求,post 请求,文件上传,文件下载等功能.
三、准备硬件环境
参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
四、软件环境
“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。
1. Luatools工具;
2. 内核固件文件(底层core固件文件):LuatOS-SoC_V2002_Air780EP;参考项目使用的内核固件;
3. luatos需要的脚本和资源文件
脚本和资源文件点我,查看demo链接
lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到Air780EP核心板,将本篇文章中演示使用的项目文件烧录到Air780EP核心板中。
五、API 说明
http 客户端:
http.request(method,url,headers,body,opts,ca_file,client_ca, client_key, client_password)
参数
传入值类型 | 解释 |
---|---|
string | 请求方法, 支持 GET/POST 等合法的 HTTP 方法 |
string | url 地址, 支持 http 和 https, 支持域名, 支持自定义端口 |
table | 请求头 可选 例如 {[“Content-Type”] = “application/x-www-form-urlencoded”} |
string/zbuff | body 可选 |
table | 额外配置 可选 包含 timeout:超时时间单位 ms 可选,默认 10 分钟,写 0 即永久等待 dst:下载路径,可选 adapter:选择使用网卡,可选 debug:是否打开 debug 信息,可选,ipv6:是否为 ipv6 默认不是,可选 callback:下载回调函数,参数 content_len:总长度 body_len:以下载长度 userdata 用户传参,可选 userdata:回调自定义传参 |
string | 服务器 ca 证书数据, 可选, 一般不需要 |
string | 客户端 ca 证书数据, 可选, 一般不需要, 双向 https 认证才需要 |
string | 客户端私钥加密数据, 可选, 一般不需要, 双向 https 认证才需要 |
string | 客户端私钥口令数据, 可选, 一般不需要, 双向 https 认证才需要 |
返回值
返回值类型 | 解释 |
---|---|
int | code , 服务器反馈的值 >=100, 最常见的是 200.如果是底层错误,例如连接失败, 返回值小于 0 |
table | headers 当 code>100 时, 代表服务器返回的头部数据 |
string/int | body 服务器响应的内容字符串,如果是下载模式, 则返回文件大小 |
创建 HTTP 客户端
-- 使用http库,需要引入sysplus库, 且需要在task内使用
require "sys"
require "sysplus"
sys.taskInit(function()
sys.wait(1000)
local code,headers,body = http.request("GET","http://www.example.com/abc").wait()
log.info("http",code,body)
end)
--[[
返回码code报错信息列表:
-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_TIMEOUT 超时, 包括连接超时,读取数据超时
-9 HTTP_ERROR_FOTA fota功能报错,通常是更新包不合法
]]
六、功能验证
6.1 GET 请求
HTTP GET 请求是一种用于从指定资源 URI(统一资源标识符)请求数据的 HTTP 方法.它通常用于请求服务器发送资源(如 HTML 页面、图片等)给客户端,且请求信息包含在 URL 中.
下面根据 demo 演示 HTTP 的 GET 请求用法,示例代码如下 (具体 demo 可以点此链接跳转)
示例如下:
function demo_http_get()
-- 最普通的Http GET请求
local code, headers, body = http.request("GET", "https://www.air32.cn/").wait()
log.info("http.get", code, headers, body)
sys.wait(100)
local code, headers, body = http.request("GET", "https://www.luatos.com/").wait()
log.info("http.get", code, headers, body)
-- 按需打印
-- code 响应值, 若大于等于 100 为服务器响应, 小于的均为错误代码
-- headers是个table, 一般作为调试数据存在
-- body是字符串. 注意lua的字符串是带长度的byte[]/char*, 是可以包含不可见字符的
-- log.info("http", code, json.encode(headers or {}), #body > 512 and #body or body)
end
sys.taskInit(function()
sys.wait(100)
-- 打印一下支持的加密套件, 通常来说, 固件已包含常见的99%的加密套件
-- if crypto.cipher_suites then
-- log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-- end
-------- HTTP 演示代码 --------------
sys.waitUntil("net_ready") -- 等联网
while 1 do
-- 演示GET请求
demo_http_get()
sys.wait(1000)
-- 打印一下内存状态
log.info("sys", rtos.meminfo("sys"))
log.info("lua", rtos.meminfo("lua"))
sys.wait(600000)
end
end)
对应 log:
6.2 POST 请求
HTTP POST 请求是一种 HTTP 方法,用于向指定的资源提交数据.与 GET 请求不同,POST 请求的数据包含在请求体中,可以提交大量数据且数据不会显示在 URL 中,常用于提交表单数据或上传文件等操作.
下面根据 demo 演示 HTTP 的 POST 请求方法提交一个表单,示例代码如下 (具体 demo 可以点此链接跳转)
示例:
function demo_http_post_form()
-- POST request 演示
local req_headers = {}
req_headers["Content-Type"] = "application/x-www-form-urlencoded"
local params = {
ABC = "123",
DEF = 345
}
local body = ""
for k, v in pairs(params) do
body = body .. tostring(k) .. "=" .. tostring(v):urlEncode() .. "&"
end
local code, headers, body = http.request("POST","http://httpbin.air32.cn/post",
req_headers,
body -- POST请求所需要的body, string, zbuff, file均可
).wait()
log.info("http.post.form", code, headers, body)
end
sys.taskInit(function()
sys.wait(100)
-- 打印一下支持的加密套件, 通常来说, 固件已包含常见的99%的加密套件
-- if crypto.cipher_suites then
-- log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-- end
-------- HTTP 演示代码 --------------
sys.waitUntil("net_ready") -- 等联网
while 1 do
-- post表单提交
demo_http_post_form()
sys.wait(1000)
-- 打印一下内存状态
log.info("sys", rtos.meminfo("sys"))
log.info("lua", rtos.meminfo("lua"))
sys.wait(600000)
end
end)
对应 log:
6.3 文件上传
HTTP POST 请求在文件上传场景中发挥着关键作用.用户通过 POST 请求可以将文件数据包含在请求体中发送给服务器,而不是像 GET 请求那样通过 URL 传递.这种方式允许上传大量数据,包括各种类型的文件,如图片、视频、文档等.服务器接收到请求后,会解析请求体中的文件数据,并存储到服务器上相应的位置.文件上传是 HTTP 应用中常见的功能.
下面根据 demo 演示 HTTP 文件上传的功能,示例代码如下 (具体 demo 可以点此链接跳转)
示例:
---- MultipartForm上传文件
-- url string 请求URL地址
-- req_headers table 请求头
-- params table 需要传输的数据参数
function postMultipartFormData(url, params)
local boundary = "----WebKitFormBoundary"..os.time()
local req_headers = {
["Content-Type"] = "multipart/form-data; boundary="..boundary,
}
local body = {}
-- 解析拼接 body
for k,v in pairs(params) do
if k=="texts" then
local bodyText = ""
for kk,vv in pairs(v) do
print(kk,vv)
bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
end
table.insert(body, bodyText)
elseif k=="files" then
local contentType =
{
txt = "text/plain", -- 文本
jpg = "image/jpeg", -- JPG 格式图片
jpeg = "image/jpeg", -- JPEG 格式图片
png = "image/png", -- PNG 格式图片
gif = "image/gif", -- GIF 格式图片
html = "image/html", -- HTML
json = "application/json" -- JSON
}
for kk,vv in pairs(v) do
if type(vv) == "table" then
for i=1, #vv do
print(kk,vv[i])
table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv[i]:match("[^%/]+%w
$").."\"\r\nContent-Type: "..contentType[vv[i]:match("%.(%w+)$
")].."\r\n\r\n")
table.insert(body, io.readFile(vv[i]))
table.insert(body, "\r\n")
end
else
print(kk,vv)
table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv:match("[^%/]+%w
$").."\"\r\nContent-Type: "..contentType[vv:match("%.(%w+)$
")].."\r\n\r\n")
table.insert(body, io.readFile(vv))
table.insert(body, "\r\n")
end
end
end
end
table.insert(body, "--"..boundary.."--\r\n")
body = table.concat(body)
log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
log.info("body: " .. body:len() .. "\r\n" .. body)
local code, headers, body = http.request("POST",url,
req_headers,
body
).wait()
log.info("http.post", code, headers, body)
end
function demo_http_post_file()
-- -- POST multipart/form-data模式 上传文件---手动拼接
local boundary = "----WebKitFormBoundary" .. os.time()
local req_headers = {
["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
}
local body = "--" .. boundary .. "\r\n" ..
"Content-Disposition: form-data; name=\"uploadFile\"; filename=\"luatos_uploadFile_TEST01.txt\"" ..
"\r\nContent-Type: text/plain\r\n\r\n" ..
"1111http_测试一二三四654zacc\r\n" ..
"--" .. boundary
log.info("headers: ", "\r\n" .. json.encode(req_headers))
log.info("body: ", "\r\n" .. body)
local code, headers, body = http.request("POST", "http://airtest.openluat.com:2900/uploadFileToStatic",
req_headers,
body -- POST请求所需要的body, string, zbuff, file均可
).wait()
log.info("http.post", code, headers, body)
-- 也可用postMultipartFormData(url, params) 上传文件
postMultipartFormData(
"http://airtest.openluat.com:2900/uploadFileToStatic",
{
-- texts =
-- {
-- ["imei"] = "862991234567890",
-- ["time"] = "20180802180345"
-- },
files =
{
["uploadFile"] = "/luadb/luatos_uploadFile.txt",
}
}
)
end
sys.taskInit(function()
sys.wait(100)
-- 打印一下支持的加密套件, 通常来说, 固件已包含常见的99%的加密套件
-- if crypto.cipher_suites then
-- log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-- end
-------- HTTP 演示代码 --------------
sys.waitUntil("net_ready") -- 等联网
while 1 do
demo_http_post_file()
sys.wait(1000)
-- 打印一下内存状态
log.info("sys", rtos.meminfo("sys"))
log.info("lua", rtos.meminfo("lua"))
sys.wait(600000)
end
end)
对应 log:
考虑例程的服务器不方面查看上传结果,所以自己找了一个回环服务器(httpbin.org 的测试服务器)来验证。httpbin.org/post 是一个回环服务器,即当向其传输文件时,服务器会将传输内容作为响应返回,因而当我们看到响应并返回的内容后,就可以判断操作是否正确。 因为我们使用”post"方法上传文件,所以 url 为 httpbin.org/post。只需要例程把网址改成 http://httpbin.org/post 即可。
-- local code, headers, body = http.request("POST","http://airtest.openluat.com:2900/uploadFileToStatic",
local code, headers, body = http.request("POST", "http://httpbin.org/post",
req_headers,
body -- POST请求所需要的body, string, zbuff, file均可
).wait()
[2024-11-11 16:23:16.614][000000006.286] D/DNS httpbin.org state 0 id 2 ipv6 0 use dns server2, try 0
[2024-11-11 16:23:16.693][000000006.377] I/DNS dns all done ,now stop
[2024-11-11 16:23:17.746][000000007.425] I/user.http.post 200 table: 00471318
[2024-11-11 16:23:17.752][000000007.426] {
"args": {},
"data": "",
"files": {
"uploadFile": "\u6d4b\u8bd5\u6587\u672c adcc1234x\u8863\u4e8c\u4e09\n\t84as1c188\n-*accxxx6-4"
},
"form": {},
"headers": {
"Content-Length": "245",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary1731313396",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-6731bef5-67791b71404cfedf68d953f2"
},
"json": null,
"origin": "223.104.84.124",
"url": "http://httpbin.org/post"
}
6.4 文件下载
HTTP 文件下载的功能可以参考示例代码,示例代码如下 (具体 demo 可以点此链接跳转)
示例:
对应 log:
示例代码中的服务器已经不能用了,所以自己又找了一个外链服务器:http://zuoye.free.fr。 首先进入外链服务器网页,首页有一个"选择文件",点击添加自己的文件 添加文件后得到外链地址: 加入自己的程序进行验证:
--文件整体下载比较简单,只需要知道资源的位置下载就可以。
local code, headers, body = http.request("GET","http://zuoye.free.fr/files/Air780EP_LuatOS.png").wait()
log.info("http.post", code, headers, body) -- 只返回code和headers
[2024-11-09 12:21:05.261][000000002.086] I/user.已联网
[2024-11-09 12:21:05.264][000000002.089] D/DNS zuoye.free.fr state 0 id 1 ipv6 0 use dns server2, try 0
[2024-11-09 12:21:05.319][000000002.170] I/DNS dns all done ,now stop
[2024-11-09 12:21:05.398][000000002.245] D/mobile TIME_SYNC 0
[2024-11-09 12:21:06.538][000000003.398] I/user.http.post 200 table: 00472820
[2024-11-09 12:21:06.540][000000003.398] 塒NG
6.5 处理 JSON 数据
处理 json 数据主要有两个函数,json.encode(t)和 json.decode(str),参考示例代码:https://gitee.com/openLuat/LuatOS-Air780EP/tree/master/demo/json
6.6 压缩和解压
这个例程用 和风天气 的 api 做演示,请求到的数据配合 miniz 库进行解压,示例代码如下 (具体 demo 可以点此链接跳转)
示例:
对应 log:
也可以参考例程:https://gitee.com/openLuat/LuatOS-Air780EP/tree/master/demo/miniz
七、总结
本文档主要介绍 4G 通信中 http 网络协议的应用.讲解了 HTTP 基本原理,GET 和 POST 请求,以及文件上传下载、JSON 数据处理和数据压缩等高级功能,直接烧录例程即可测试,旨在实现高效、安全的数据传输.
八、常见问题
8.1 HTTP 应用一直连接失败
HTTP 一直请求应答失败,则尝试使用如下手段恢复:
1、使用 RESET 引脚复位模块
2、极端情况下,直接给模块断电,再上电,POWER KEY 引脚拉低开机
8.2 为什么我只发了 10 字节消息,100 次却消耗了那么多流量?
因为还有 HTTP 自带的请求头.如何统计流量
8.3 为什么频繁请求会失败?
建议一个 http 连接返回请求结果之后,再去请求下一个连接;不要使用循环定时器方式不断的发起新的 http 请求.
8.4 专网卡访问白名单
用定向 Ip 的物联网卡,需要把域名或 IP 加入白名单才能使用.如果不加入白名单会出现无法访问服务器的情况.
给读者的话
本篇文章由公帅开发;
本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;
请登录合宙技术交流论坛,点击文档找错赢奖金-Air780EP-LuatOS-软件指南-网络驱动-HTTP通信;
用截图标注+文字描述的方式跟帖回复,记录清楚您发现的问题;
我们会迅速核实并且修改文档;
同时也会为您累计找错积分,您还可能赢取月度找错奖金!