http_app.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. --[[
  2. @module http_app
  3. @summary http应用功能模块
  4. @version 1.0
  5. @date 2025.08.01
  6. @author 马梦阳
  7. @usage
  8. 本文件为http应用功能模块,核心业务逻辑为:基于不同的应用场景,演示http核心库的使用方式;
  9. http核心库和httpplus扩展库的区别如下:
  10. | 区别项 | http核心库 | httpplus扩展库 |
  11. | --------------------------------- | ------------------------------------------------------------------------- | ---------------------------- |
  12. | 文件上传 | 文件最大64KB | 只要内存够用,文件大小不限 |
  13. | 文件下载 | 支持,只要文件系统空间够用,文件大小不限 | 不支持 |
  14. | http header的key: value的限制 | 所有header的value总长度不能超过4KB,单个header的value长度不能超过1KB | 只要内存够用,header长度不限 |
  15. | 鉴权URL自动识别 | 不支持 | 支持 |
  16. | 接收到的body数据存储支持zbuff | 不支持 | 支持,可以直接传输给uart等库 |
  17. | 接收到的body数据存储到内存中 | 最大支持32KB | 只要内存够用,大小不限 |
  18. | chunk编码 | 支持 | 不支持 |
  19. 本文件没有对外接口,直接在main.lua中require "http_app"就可以加载运行;
  20. ]]
  21. -- http下载数据回调函数
  22. -- content_len:number类型,数据总长度
  23. -- body_len:number类型,已经下载的数据长度
  24. -- userdata:下载回调函数使用的用户自定义回调参数
  25. -- 每收到一包body数据,就会调用一次http_cbfunc回调函数
  26. local function http_cbfunc(content_len, body_len, userdata)
  27. log.info("http_cbfunc", content_len, body_len, userdata)
  28. end
  29. -- 普通的http get请求功能演示
  30. -- 请求的body数据保存到内存变量中,在内存够用的情况下,最大支持32KB的数据存储到内存中
  31. -- timeout可以设置超时时间
  32. -- callback可以设置回调函数,可用于实时检测body数据的下载进度
  33. local function http_app_get()
  34. -- https get请求https://www.air32.cn/网页内容
  35. -- 应答体为chunk编码
  36. -- 如果请求成功,请求的数据保存到body中
  37. local code, headers, body = http.request("GET", "http://www.air32.cn/").wait()
  38. log.info("http_app_get1",
  39. code==200 and "success" or "error",
  40. code,
  41. json.encode(headers or {}),
  42. body and (body:len()>512 and body:len() or body) or "nil")
  43. -- https get请求https://www.luatos.com/网页内容,超时时间为10秒
  44. -- 请求超时时间为10秒,用户自己写代码时,不要照抄10秒,根据自己业务逻辑的需要设置合适的超时时间
  45. -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get2"
  46. -- 如果请求成功,请求的数据保存到body中
  47. code, headers, body = http.request("GET", "https://www.luatos.com/", nil, nil, {timeout=10000, userdata="http_app_get2", callback=http_cbfunc}).wait()
  48. log.info("http_app_get2",
  49. code==200 and "success" or "error",
  50. code,
  51. json.encode(headers or {}),
  52. body and (body:len()>512 and body:len() or body) or "nil")
  53. -- http get请求http://httpbin.air32.cn/get网页内容,超时时间为3秒
  54. -- 请求超时时间为3秒,用户自己写代码时,不要照抄3秒,根据自己业务逻辑的需要设置合适的超时时间
  55. -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get3"
  56. -- 如果请求成功,请求的数据保存到body中
  57. code, headers, body = http.request("GET", "http://httpbin.air32.cn/get", nil, nil, {timeout=3000, userdata="http_app_get3", callback=http_cbfunc}).wait()
  58. log.info("http_app_get3",
  59. code==200 and "success" or "error",
  60. code,
  61. json.encode(headers or {}),
  62. body and (body:len()>512 and body:len() or body) or "nil")
  63. end
  64. -- http get下载压缩数据的功能演示
  65. local function http_app_get_gzip()
  66. -- https get请求https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de网页内容
  67. -- 如果请求成功,请求的数据保存到body中
  68. local code, headers, body = http.request("GET", "https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de").wait()
  69. log.info("http_app_get_gzip",
  70. code==200 and "success" or "error",
  71. code,
  72. json.encode(headers or {}),
  73. body and (body:len()>512 and body:len() or body) or "nil")
  74. -- 如果请求成功
  75. if code == 200 then
  76. -- 从body的第11个字节开始解压缩
  77. local uncompress_data = miniz.uncompress(body:sub(11,-1), 0)
  78. if not uncompress_data then
  79. log.error("http_app_get_gzip uncompress error")
  80. return
  81. end
  82. local json_data = json.decode(uncompress_data)
  83. if not json_data then
  84. log.error("http_app_get_gzip json.decode error")
  85. return
  86. end
  87. log.info("http_app_get_gzip json_data", json_data)
  88. log.info("http_app_get_gzip", "和风天气", json_data.code)
  89. if json_data.now then
  90. log.info("http_app_get_gzip", "和风天气", "天气", json_data.now.text)
  91. log.info("http_app_get_gzip", "和风天气", "温度", json_data.now.temp)
  92. end
  93. end
  94. end
  95. -- http get下载数据保存到文件中的功能演示
  96. -- 请求的body数据保存到文件中,在文件系统够用的情况下,文件大小不限
  97. -- timeout可以设置超时时间
  98. -- callback可以设置回调函数,可用于实时检测文件下载进度
  99. local function http_app_get_file()
  100. -- 创建/http_download目录,用来存放通过http下载的文件
  101. -- 重复创建目录会返回失败
  102. -- 在创建目录之前可以使用io.dexist判断下目录是否存在
  103. -- io.dexist接口仅新版本支持(Air8000系列需要使用V2014及以上固件)
  104. -- 若有报错提示,请检查是否是因为使用了旧版本的内核固件
  105. local download_dir = "/http_download/"
  106. if not io.dexist(download_dir) then
  107. local result, reason = io.mkdir(download_dir)
  108. if not result then
  109. log.error("http_app_get_file io.mkdir error", reason)
  110. end
  111. end
  112. local file_path = download_dir.."get_file1.html"
  113. -- https get请求https://www.air32.cn/网页内容
  114. -- 如果请求成功,请求的数据保存到文件file_path中
  115. local code, headers, body_size = http.request("GET", "https://www.air32.cn/", nil, nil, {dst=file_path}).wait()
  116. log.info("http_app_get_file1",
  117. code==200 and "success" or "error",
  118. code,
  119. json.encode(headers or {}),
  120. body_size)
  121. -- 如果下载成功
  122. if code==200 then
  123. -- 读取文件大小
  124. local size = io.fileSize(file_path)
  125. log.info("http_app_get_file1", "io.fileSize="..size)
  126. if size~=body_size then
  127. log.error("io.fileSize doesn't equal with body_size, error", size, body_size)
  128. end
  129. --文件使用完之后,如果以后不再用到,根据需要可以自行删除
  130. os.remove(file_path)
  131. end
  132. file_path = download_dir.."get_file2.html"
  133. -- https get请求https://www.luatos.com/网页内容
  134. -- 请求超时时间为10秒,用户自己写代码时,不要照抄10秒,根据自己业务逻辑的需要设置合适的超时时间
  135. -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get_file2"
  136. -- 如果请求成功,请求的数据保存到文件file_path中
  137. code, headers, body_size = http.request("GET", "https://www.luatos.com/", nil, nil, {dst=file_path, timeout=10000, userdata="http_app_get_file2", callback=http_cbfunc}).wait()
  138. log.info("http_app_get_file2",
  139. code==200 and "success" or "error",
  140. code,
  141. json.encode(headers or {}),
  142. body_size)
  143. -- 如果下载成功
  144. if code==200 then
  145. -- 读取文件大小
  146. local size = io.fileSize(file_path)
  147. log.info("http_app_get_file2", "io.fileSize="..size)
  148. if size~=body_size then
  149. log.error("io.fileSize doesn't equal with body_size, error", size, body_size)
  150. end
  151. --文件使用完之后,如果以后不再用到,根据需要可以自行删除
  152. os.remove(file_path)
  153. end
  154. file_path = download_dir.."get_file3.html"
  155. -- http get请求http://httpbin.air32.cn/get网页内容,超时时间为3秒
  156. -- 请求超时时间为3秒,用户自己写代码时,不要照抄3秒,根据自己业务逻辑的需要设置合适的超时时间
  157. -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get_file3"
  158. -- 如果请求成功,请求的数据保存到文件file_path中
  159. code, headers, body_size = http.request("GET", "http://httpbin.air32.cn/get", nil, nil, {dst=file_path, timeout=3000, userdata="http_app_get_file3", callback=http_cbfunc}).wait()
  160. log.info("http_app_get_file3",
  161. code==200 and "success" or "error",
  162. code,
  163. json.encode(headers or {}),
  164. body_size)
  165. -- 如果下载成功
  166. if code==200 then
  167. -- 读取文件大小
  168. local size = io.fileSize(file_path)
  169. log.info("http_app_get_file3", "io.fileSize="..size)
  170. if size~=body_size then
  171. log.error("io.fileSize doesn't equal with body_size, error", size, body_size)
  172. end
  173. --文件使用完之后,如果以后不再用到,根据需要可以自行删除
  174. os.remove(file_path)
  175. end
  176. end
  177. -- http post提交表单数据功能演示
  178. local function http_app_post_form()
  179. local params = {
  180. username = "LuatOS",
  181. password = "123456"
  182. }
  183. local body = ""
  184. -- 拼接成url编码的键值对的形式
  185. for k, v in pairs(params) do
  186. body = body .. k .. "=" .. tostring(v):urlEncode() .. "&"
  187. end
  188. -- 删除最后一位的&字符,最终为string类型的username=LuatOS&password=123456
  189. body = body:sub(1,-2)
  190. -- http post提交表单数据
  191. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的表单数据后,还会下发同样的表单数据给设备
  192. -- ["Content-Type"] = "application/x-www-form-urlencoded" 表示post提交的body数据格式为url编码的键值对形式的表单数据
  193. -- 如果请求成功,服务器应答的数据会保存到resp_body中
  194. local code, headers, resp_body = http.request("POST", "http://httpbin.air32.cn/post", {["Content-Type"] = "application/x-www-form-urlencoded"}, body).wait()
  195. log.info("http_app_post_form",
  196. code==200 and "success" or "error",
  197. code,
  198. json.encode(headers or {}),
  199. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  200. end
  201. -- http post提交json数据功能演示
  202. local function http_app_post_json()
  203. local params = {
  204. username = "LuatOS",
  205. password = "123456"
  206. }
  207. local body = json.encode(params)
  208. -- http post提交json数据
  209. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的json数据后,还会下发同样的json数据给设备
  210. -- ["Content-Type"] = "application/json" 表示post提交的body数据格式为json格式的数据
  211. -- 如果请求成功,服务器应答的数据会保存到resp_body中
  212. local code, headers, resp_body = http.request("POST", "http://httpbin.air32.cn/post", {["Content-Type"] = "application/json"}, body).wait()
  213. log.info("http_app_post_json",
  214. code==200 and "success" or "error",
  215. code,
  216. json.encode(headers or {}),
  217. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  218. end
  219. -- http post提交纯文本数据功能演示
  220. local function http_app_post_text()
  221. -- http post提交纯文本数据
  222. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的纯文本数据后,还会下发同样的纯文本数据给设备
  223. -- ["Content-Type"] = "text/plain" 表示post提交的body数据格式为纯文本格式的数据
  224. -- 如果请求成功,服务器应答的数据会保存到resp_body中
  225. local code, headers, resp_body = http.request("POST", "http://httpbin.air32.cn/post", {["Content-Type"] = "text/plain"}, "This is a raw text message from LuatOS device").wait()
  226. log.info("http_app_post_text",
  227. code==200 and "success" or "error",
  228. code,
  229. json.encode(headers or {}),
  230. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  231. end
  232. -- http post提交xml数据功能演示
  233. local function http_app_post_xml()
  234. -- [=[ 和 ]=] 之间是一个多行字符串
  235. local body = [=[
  236. <?xml version="1.0" encoding="UTF-8"?>
  237. <user>
  238. <name>LuatOS</name>
  239. <password>123456</password>
  240. </user>
  241. ]=]
  242. -- http post提交xml数据
  243. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的xml数据后,还会下发同样的xml数据给设备
  244. -- ["Content-Type"] = "text/xml" 表示post提交的body数据格式为xml格式的数据
  245. -- 如果请求成功,服务器应答的数据会保存到resp_body中
  246. local code, headers, resp_body = http.request("POST", "http://httpbin.air32.cn/post", {["Content-Type"] = "text/xml"}, body).wait()
  247. log.info("http_app_post_xml",
  248. code==200 and "success" or "error",
  249. code,
  250. json.encode(headers or {}),
  251. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  252. end
  253. -- http post提交原始二进制数据功能演示
  254. local function http_app_post_binary()
  255. local body = io.readFile("/luadb/logo.jpg")
  256. -- http post提交原始二进制数据
  257. -- http://upload.air32.cn/api/upload/jpg为jpg图片上传测试服务器
  258. -- 此处将logo.jpg的原始二进制数据做为body上传到服务器
  259. -- 上传成功后,电脑上浏览器打开https://www.air32.cn/upload/data/jpg/,打开对应的测试日期目录,点击具体的测试时间照片,可以查看上传的照片
  260. -- ["Content-Type"] = "application/octet-stream" 表示post提交的body数据格式为原始二进制格式的数据
  261. -- 如果请求成功,服务器应答的数据会保存到resp_body中
  262. local code, headers, resp_body = http.request("POST", "http://upload.air32.cn/api/upload/jpg", {["Content-Type"] = "application/octet-stream"}, body).wait()
  263. log.info("http_app_post_binary",
  264. code==200 and "success" or "error",
  265. code,
  266. json.encode(headers or {}),
  267. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  268. end
  269. local function post_multipart_form_data(url, params)
  270. local boundary = "----WebKitFormBoundary"..os.time()
  271. local req_headers = {
  272. ["Content-Type"] = "multipart/form-data; boundary="..boundary,
  273. }
  274. local body = {}
  275. -- 解析拼接 body
  276. for k,v in pairs(params) do
  277. if k=="texts" then
  278. local bodyText = ""
  279. for kk,vv in pairs(v) do
  280. print(kk,vv)
  281. bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
  282. end
  283. table.insert(body, bodyText)
  284. elseif k=="files" then
  285. local contentType =
  286. {
  287. txt = "text/plain", -- 文本
  288. jpg = "image/jpeg", -- JPG 格式图片
  289. jpeg = "image/jpeg", -- JPEG 格式图片
  290. png = "image/png", -- PNG 格式图片
  291. gif = "image/gif", -- GIF 格式图片
  292. html = "image/html", -- HTML
  293. json = "application/json", -- JSON
  294. }
  295. for kk,vv in pairs(v) do
  296. if type(vv) == "table" then
  297. for i=1, #vv do
  298. print(kk,vv[i])
  299. 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")
  300. table.insert(body, io.readFile(vv[i]))
  301. table.insert(body, "\r\n")
  302. end
  303. else
  304. print(kk,vv)
  305. 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")
  306. table.insert(body, io.readFile(vv))
  307. table.insert(body, "\r\n")
  308. end
  309. end
  310. end
  311. end
  312. table.insert(body, "--"..boundary.."--\r\n")
  313. body = table.concat(body)
  314. log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
  315. log.info("body: " .. body:len() .. "\r\n" .. body)
  316. local code, headers, resp_body = http.request("POST", url, req_headers, body).wait()
  317. log.info("post_multipart_form_data",
  318. code==200 and "success" or "error",
  319. code,
  320. json.encode(headers or {}),
  321. resp_body and (resp_body:len()>512 and resp_body:len() or resp_body) or "nil")
  322. end
  323. -- http post文件上传功能演示
  324. local function http_app_post_file()
  325. -- 此接口post_multipart_form_data支持单文件上传、多文件上传、单文本上传、多文本上传、单/多文本+单/多文件上传
  326. -- http://airtest.openluat.com:2900/uploadFileToStatic 仅支持单文件上传,并且上传的文件name必须使用"uploadFile"
  327. -- 所以此处仅演示了单文件上传功能,并且"uploadFile"不能改成其他名字,否则会出现上传失败的应答
  328. -- 如果你自己的http服务支持更多类型的文本/文件混合上传,可以打开注释自行验证
  329. post_multipart_form_data(
  330. "http://airtest.openluat.com:2900/uploadFileToStatic",
  331. {
  332. -- texts =
  333. -- {
  334. -- ["username"] = "LuatOS",
  335. -- ["password"] = "123456"
  336. -- },
  337. files =
  338. {
  339. ["uploadFile"] = "/luadb/logo.jpg",
  340. -- ["logo1.jpg"] = "/luadb/logo.jpg",
  341. }
  342. }
  343. )
  344. end
  345. -- https+证书校验 get请求功能演示
  346. -- 请求的body数据保存到内存变量中,在内存够用的情况下,最大支持32KB的数据存储到内存中
  347. local function http_app_ca_get()
  348. -- 用来验证server证书是否合法的ca证书文件为baidu_parent_ca.crt
  349. -- 此ca证书的有效期截止到2028年11月21日
  350. -- 将这个ca证书文件的内容读取出来,赋值给server_ca_cert
  351. -- 注意:此处的ca证书文件仅用来验证baidu网站的server证书
  352. -- baidu网站的server证书有效期截止到2026年8月10日
  353. -- 在有效期之前,baidu会更换server证书,如果server证书更换后,此处验证使用的baidu_parent_ca.crt也可能需要更换
  354. -- 使用电脑上的网页浏览器访问https://www.baidu.com,可以实时看到baidu的server证书以及baidu_parent_ca.crt
  355. -- 如果你使用的是自己的server,要替换为自己server证书对应的ca证书文件
  356. local server_ca_cert = io.readFile("/luadb/baidu_parent_ca.crt")
  357. -- https get请求https://www.bidu.cn/网页内容
  358. -- 如果请求成功,请求的数据保存到body中
  359. local code, headers, body = http.request("GET", "https://www.baidu.com/", nil, nil, nil, server_ca_cert).wait()
  360. log.info("http_app_ca_get",
  361. code==200 and "success" or "error",
  362. code,
  363. json.encode(headers or {}),
  364. body and (body:len()>512 and body:len() or body) or "nil")
  365. end
  366. -- http app task 的任务处理函数
  367. local function http_app_task_func()
  368. while true do
  369. -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
  370. while not socket.adapter(socket.dft()) do
  371. log.warn("http_app_task_func", "wait IP_READY", socket.dft())
  372. -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
  373. -- 或者等待1秒超时退出阻塞等待状态;
  374. -- 注意:此处的1000毫秒超时不要修改的更长;
  375. -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
  376. -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
  377. -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
  378. sys.waitUntil("IP_READY", 1000)
  379. end
  380. -- 检测到了IP_READY消息
  381. log.info("http_app_task_func", "recv IP_READY", socket.dft())
  382. -- 普通的http get请求功能演示
  383. http_app_get()
  384. -- http get下载压缩数据的功能演示
  385. http_app_get_gzip()
  386. -- http get下载数据保存到文件中的功能演示
  387. http_app_get_file()
  388. -- http post提交表单数据功能演示
  389. http_app_post_form()
  390. -- http post提交json数据功能演示
  391. http_app_post_json()
  392. -- http post提交纯文本数据功能演示
  393. http_app_post_text()
  394. -- http post提交xml数据功能演示
  395. http_app_post_xml()
  396. -- http post提交原始二进制数据功能演示
  397. http_app_post_binary()
  398. -- http post文件上传功能演示
  399. http_app_post_file()
  400. -- https+证书校验 get请求功能演示
  401. http_app_ca_get()
  402. -- 60秒之后,循环测试
  403. sys.wait(60000)
  404. end
  405. end
  406. --创建并且启动一个task
  407. --运行这个task的处理函数http_app_task_func
  408. sys.taskInit(http_app_task_func)