httpplus_app.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. --[[
  2. @module httpplus
  3. @summary httpplus应用功能模块
  4. @version 1.0
  5. @date 2025.08.06
  6. @author 马梦阳
  7. @usage
  8. 本文件为httpplus应用功能模块,核心业务逻辑为:基于不同的应用场景,演示httpplus扩展库的使用方式;
  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 "httpplus_app"就可以加载运行;
  20. ]]
  21. --[[
  22. 此处先详细解释下httpplus.request接口的使用方法
  23. 接口定义:
  24. httpplus.request(opts)
  25. 使用方法:
  26. local code, response = httpplus.request(opts)
  27. 只能在task中使用
  28. 发送http请求到服务器,等待服务器的http应答,此处会阻塞当前task,等待整个过程成功结束或者出现错误异常结束或者超时结束
  29. 参数定义:
  30. opts,table类型,表示HTTP请求参数,包含以下内容
  31. {
  32. url -- string类型,必须包含此参数,表示HTTP请求URL地址,支持HTTP、HTTPS,支持域名、IP地址,支持自定义端口,标准的HTTP URL格式都支持
  33. method -- stirng或者nil类型,可选包含此参数,表示HTTP请求方法,支持"GET"、"POST"、"HEAD"等所有HTTP请求方法,如果没有传入此参数或者传入了nil类型,则使用默认值,默认值分为以下两种情况:
  34. -- 如果没有设置files,forms,body,bodyfile参数,则默认为"GET"
  35. -- 如果至少设置了files,forms,body,bodyfile中的一种参数,则默认为"POST"
  36. headers -- table或者nil类型,可选包含此参数,表示自定义的一个或者多个HTTP请求头,例如 {["self_defined_key1"] = "self_defined_value1", ["self_defined_key2"] = "self_defined_value2"}
  37. timeout -- number或者nil类型,单位秒,可选包含此参数,表示从发送请求到读取到服务器响应整个过程的超时时间,如果传入0,表示永久等待;如果没有传入此参数或者传入nil,则使用默认值30秒
  38. files -- table或者nil类型,可选包含此参数,表示POST上传的一个或者多个文件列表,键值对的形式,若存在本参数,会自动强制以multipart/form-data形式上传;例如
  39. -- {
  40. -- ["uploadFile"] = "/luadb/logo.jpg",
  41. -- ["logo1.jpg"] = "/luadb/logo.jpg",
  42. -- }
  43. forms -- table或者nil类型,可选包含此参数,表示POST上传的一个或者多个表单参数列表,键值对的形式
  44. -- 若存在本参数并且不存在files参数,会自动强制以application/x-www-form-urlencoded形式上传
  45. -- 若存在本参数并且存在files参数,会自动强制以multipart/form-data形式上传,也就是说支持同时上传文件和表单参数
  46. -- 例如:
  47. -- {
  48. -- ["username"] = "LuatOS",
  49. -- ["password"] = "123456",
  50. -- }
  51. body -- string,zbuff,table或者nil类型,可选包含此参数,表示自定义的body内容, 不能与files或者forms同时存在
  52. bodyfile -- string或者nil类型,可选包含此参数,表示要上传的一个文件的路径,会自动读取文件中的内容进行上传
  53. -- 不能与files或者forms同时存在
  54. -- 可以与body同时存在,与body同时存在时, 优先级高于body参数,也就是说,bodyfile对应的文件路径中的内容在body参数对应的内容之前
  55. debug -- bool或者nil类型,可选包含此参数,表示调试开关,true表示打开debug调试信息日志,false表示关闭debug调试信息日志,如果没有传入此参数或者传入了nil类型,则使用默认值false
  56. try_ipv6 -- bool或者nil类型,可选包含此参数,表示是否优先尝试ipv6地址,true表示优先尝试使用ipv6,false表示不尝试使用ipv6,如果没有传入此参数或者传入了nil类型,则使用默认值false
  57. adapter -- number或者nil类型,可选包含此参数,表示使用的网卡ID,例如4G网卡,SPI外挂以太网卡,WIFI网卡等;如果没有传入此参数,内核固件会自动选择当前时间点其他功能模块设置的默认网卡
  58. -- 除非你HTTP请求时,一定要使用某一种网卡,才设置此参数;如果没什么特别要求,不要使用此参数,使用系统中设置的默认网卡即可
  59. -- 这个参数和本demo中的netdrv_device.lua关系比较大,netdrv_device会设置默认网卡,此处http不要设置adapter参数,直接使用netdrv_device设置的默认网卡就行
  60. }
  61. 返回值定义:
  62. httpplus.request(opts)有两个返回值code,response
  63. code表示执行结果,number类型,有以下两种含义:
  64. 1、code大于等于100时,表示服务器返回的HTTP状态码,例如200表示成功,详细说明可以通过搜索引擎搜索“HTTP状态码”自行了解
  65. 2、code小于0时,表示内核固件中检测到通信异常,有如下几种:
  66. -1 HTTP_ERROR_STATE 错误的状态, 一般是底层异常,请报issue
  67. -2 HTTP_ERROR_HEADER 错误的响应头部, 通常是服务器问题
  68. -3 HTTP_ERROR_BODY 错误的响应体,通常是服务器问题
  69. -4 HTTP_ERROR_CONNECT 连接服务器失败, 未联网,地址错误,域名错误
  70. -5 HTTP_ERROR_CLOSE 提前断开了连接, 网络或服务器问题
  71. -6 HTTP_ERROR_RX 接收数据报错, 网络问题
  72. -7 HTTP_ERROR_DOWNLOAD 下载文件过程报错, 网络问题或下载路径问题
  73. -8 HTTP_ERROR_TIMEOUT 超时, 包括连接超时,读取数据超时
  74. -9 HTTP_ERROR_FOTA fota功能报错,通常是更新包不合法
  75. response有以下两种含义
  76. 1、当code的返回值大于等于100时,response为table类型,包含以下两项内容
  77. {
  78. headers = {}, -- table类型,一个或者多个应答头,键值对的形式,可以使用json.encode(response.headers)在日志中打印
  79. body = , -- zbuff类型,应答体数据;通过zbuff的query函数,可以转化为string类型:response.body:query();也可以通过uart.tx等支持zbuff的函数直接使用,例如uart.tx(1, response.body)
  80. }
  81. 2、当code的返回值小于0时,response为nil
  82. ]]
  83. local httpplus = require "httpplus"
  84. -- 普通的http get请求功能演示
  85. -- 请求的body数据保存到内存变量中,在内存够用的情况下,长度不限
  86. -- timeout可以设置超时时间
  87. local function httpplus_app_get()
  88. local body
  89. -- https get请求https://httpbin.air32.cn/get网页内容
  90. -- 如果请求成功,请求的数据保存到response.body中
  91. local code, response = httpplus.request({url="https://httpbin.air32.cn/get"})
  92. log.info("httpplus_app_get1", code==200 and "success" or "error", code)
  93. if code==200 then
  94. log.info("httpplus_app_get1 headers", json.encode(response.headers or {}))
  95. body = response.body:query()
  96. log.info("httpplus_app_get1 body", body and (body:len()>512 and body:len() or body) or "nil")
  97. end
  98. -- http get请求http://httpbin.air32.cn/get网页内容,超时时间为3秒
  99. -- 请求超时时间为3秒,用户自己写代码时,不要照抄3秒,根据自己业务逻辑的需要设置合适的超时时间
  100. -- 如果请求成功,请求的数据保存到body中
  101. code, response = httpplus.request({url="http://httpbin.air32.cn/get", timeout=3})
  102. log.info("httpplus_app_get2", code==200 and "success" or "error", code)
  103. if code==200 then
  104. log.info("httpplus_app_get2 headers", json.encode(response.headers or {}))
  105. body = response.body:query()
  106. log.info("httpplus_app_get2 body", body and (body:len()>512 and body:len() or body) or "nil")
  107. end
  108. end
  109. -- http get下载压缩数据的功能演示
  110. local function httpplus_app_get_gzip()
  111. local body
  112. -- https get请求https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de网页内容,超时时间为3秒
  113. -- 如果请求成功,请求的数据保存到response.body中
  114. local code, response = httpplus.request({url="https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de"})
  115. log.info("httpplus_app_get_gzip", code==200 and "success" or "error", code)
  116. if code==200 then
  117. log.info("httpplus_app_get_gzip headers", json.encode(response.headers or {}))
  118. body = response.body:query()
  119. log.info("httpplus_app_get_gzip body", body and (body:len()>512 and body:len() or body) or "nil")
  120. end
  121. -- 如果请求成功
  122. if code == 200 then
  123. -- 从body的第11个字节开始解压缩
  124. local uncompress_data = miniz.uncompress(body:sub(11,-1), 0)
  125. if not uncompress_data then
  126. log.error("httpplus_app_get_gzip uncompress error")
  127. return
  128. end
  129. local json_data = json.decode(uncompress_data)
  130. if not json_data then
  131. log.error("httpplus_app_get_gzip json.decode error")
  132. return
  133. end
  134. log.info("httpplus_app_get_gzip json_data", json_data)
  135. log.info("httpplus_app_get_gzip", "和风天气", json_data.code)
  136. if json_data.now then
  137. log.info("httpplus_app_get_gzip", "和风天气", "天气", json_data.now.text)
  138. log.info("httpplus_app_get_gzip", "和风天气", "温度", json_data.now.temp)
  139. end
  140. end
  141. end
  142. -- http post提交表单数据功能演示
  143. local function httpplus_app_post_form()
  144. -- http post提交表单数据
  145. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的表单数据后,还会下发同样的表单数据给设备
  146. -- 如果请求成功,服务器应答的数据会保存到response.body中
  147. local code, response = httpplus.request(
  148. {
  149. url = "http://httpbin.air32.cn/post",
  150. forms = {username="LuatOS", password="123456"}
  151. })
  152. log.info("httpplus_app_post_form", code==200 and "success" or "error", code)
  153. if code==200 then
  154. log.info("httpplus_app_post_form headers", json.encode(response.headers or {}))
  155. local body = response.body:query()
  156. log.info("httpplus_app_post_form body", body and (body:len()>512 and body:len() or body) or "nil")
  157. end
  158. end
  159. -- http post提交json数据功能演示
  160. local function httpplus_app_post_json()
  161. local params = {
  162. username = "LuatOS",
  163. password = "123456"
  164. }
  165. -- http post提交json数据
  166. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的json数据后,还会下发同样的json数据给设备
  167. -- ["Content-Type"] = "application/json" 表示post提交的body数据格式为json格式的数据
  168. -- 如果请求成功,服务器应答的数据会保存到response.body中
  169. local code, response = httpplus.request(
  170. {
  171. method = "POST",
  172. url = "http://httpbin.air32.cn/post",
  173. headers = {["Content-Type"] = "application/json"},
  174. body = json.encode(params)
  175. })
  176. log.info("httpplus_app_post_json", code==200 and "success" or "error", code)
  177. if code==200 then
  178. log.info("httpplus_app_post_json headers", json.encode(response.headers or {}))
  179. local body = response.body:query()
  180. log.info("httpplus_app_post_json body", body and (body:len()>512 and body:len() or body) or "nil")
  181. end
  182. end
  183. -- http post提交纯文本数据功能演示
  184. local function httpplus_app_post_text()
  185. -- http post提交纯文本数据
  186. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的纯文本数据后,还会下发同样的纯文本数据给设备
  187. -- ["Content-Type"] = "text/plain" 表示post提交的body数据格式为纯文本格式的数据
  188. -- 如果请求成功,服务器应答的数据会保存到response.body中
  189. local code, response = httpplus.request(
  190. {
  191. method = "POST",
  192. url = "http://httpbin.air32.cn/post",
  193. headers = {["Content-Type"] = "text/plain"},
  194. body = "This is a raw text message from LuatOS device"
  195. })
  196. log.info("httpplus_app_post_text", code==200 and "success" or "error", code)
  197. if code==200 then
  198. log.info("httpplus_app_post_text headers", json.encode(response.headers or {}))
  199. local body = response.body:query()
  200. log.info("httpplus_app_post_text body", body and (body:len()>512 and body:len() or body) or "nil")
  201. end
  202. end
  203. -- http post提交xml数据功能演示
  204. local function httpplus_app_post_xml()
  205. -- [=[ 和 ]=] 之间是一个多行字符串
  206. local body = [=[
  207. <?xml version="1.0" encoding="UTF-8"?>
  208. <user>
  209. <name>LuatOS</name>
  210. <password>123456</password>
  211. </user>
  212. ]=]
  213. -- http post提交xml数据
  214. -- http://httpbin.air32.cn/post为回环测试服务器,服务器收到post提交的xml数据后,还会下发同样的xml数据给设备
  215. -- ["Content-Type"] = "text/xml" 表示post提交的body数据格式为xml格式的数据
  216. -- 如果请求成功,服务器应答的数据会保存到response.body中
  217. local code, response = httpplus.request(
  218. {
  219. method = "POST",
  220. url = "http://httpbin.air32.cn/post",
  221. headers = {["Content-Type"] = "text/xml"},
  222. body = body
  223. })
  224. log.info("httpplus_app_post_xml", code==200 and "success" or "error", code)
  225. if code==200 then
  226. log.info("httpplus_app_post_xml headers", json.encode(response.headers or {}))
  227. body = response.body:query()
  228. log.info("httpplus_app_post_xml body", body and (body:len()>512 and body:len() or body) or "nil")
  229. end
  230. end
  231. -- http post提交原始二进制数据功能演示
  232. local function httpplus_app_post_binary()
  233. local body = io.readFile("/luadb/logo.jpg")
  234. -- http post提交原始二进制数据
  235. -- http://upload.air32.cn/api/upload/jpg为jpg图片上传测试服务器
  236. -- 此处将logo.jpg的原始二进制数据做为body上传到服务器
  237. -- 上传成功后,电脑上浏览器打开https://www.air32.cn/upload/data/jpg/,打开对应的测试日期目录,点击具体的测试时间照片,可以查看上传的照片
  238. -- ["Content-Type"] = "application/octet-stream" 表示post提交的body数据格式为原始二进制格式的数据
  239. -- 如果请求成功,服务器应答的数据会保存到response.body中
  240. local code, response = httpplus.request(
  241. {
  242. method = "POST",
  243. url = "http://upload.air32.cn/api/upload/jpg",
  244. headers = {["Content-Type"] = "application/octet-stream"},
  245. body = body
  246. })
  247. log.info("httpplus_app_post_binary", code==200 and "success" or "error", code)
  248. if code==200 then
  249. log.info("httpplus_app_post_binary headers", json.encode(response.headers or {}))
  250. body = response.body:query()
  251. log.info("httpplus_app_post_binary body", body and (body:len()>512 and body:len() or body) or "nil")
  252. end
  253. end
  254. -- http post文件上传功能演示
  255. local function httpplus_app_post_file()
  256. -- hhtplus.request接口支持单文件上传、多文件上传、单文本上传、多文本上传、单/多文本+单/多文件上传
  257. -- http://airtest.openluat.com:2900/uploadFileToStatic 仅支持单文件上传,并且上传的文件name必须使用"uploadFile"
  258. -- 所以此处仅演示了单文件上传功能,并且"uploadFile"不能改成其他名字,否则会出现上传失败的应答
  259. -- 如果你自己的http服务支持更多类型的文本/文件混合上传,可以打开注释自行验证
  260. local code, response = httpplus.request(
  261. {
  262. url = "http://airtest.openluat.com:2900/uploadFileToStatic",
  263. files =
  264. {
  265. ["uploadFile"] = "/luadb/logo.jpg",
  266. -- ["logo1.jpg"] = "/luadb/logo.jpg",
  267. },
  268. -- forms =
  269. -- {
  270. -- ["username"] = "LuatOS",
  271. -- ["password"] = "123456",
  272. -- },
  273. })
  274. log.info("httpplus_app_post_file", code==200 and "success" or "error", code)
  275. if code==200 then
  276. log.info("httpplus_app_post_file headers", json.encode(response.headers or {}))
  277. local body = response.body:query()
  278. log.info("httpplus_app_post_file body", body and (body:len()>512 and body:len() or body) or "nil")
  279. end
  280. end
  281. -- http app task 的任务处理函数
  282. local function httpplus_app_task_func()
  283. while true do
  284. -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
  285. while not socket.adapter(socket.dft()) do
  286. log.warn("httpplus_app_task_func", "wait IP_READY", socket.dft())
  287. -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
  288. -- 或者等待1秒超时退出阻塞等待状态;
  289. -- 注意:此处的1000毫秒超时不要修改的更长;
  290. -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
  291. -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
  292. -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
  293. sys.waitUntil("IP_READY", 1000)
  294. end
  295. -- 检测到了IP_READY消息
  296. log.info("httpplus_app_task_func", "recv IP_READY", socket.dft())
  297. -- 普通的http get请求功能演示
  298. httpplus_app_get()
  299. -- http get下载压缩数据的功能演示
  300. httpplus_app_get_gzip()
  301. -- http post提交表单数据功能演示
  302. httpplus_app_post_form()
  303. -- -- http post提交json数据功能演示
  304. httpplus_app_post_json()
  305. -- http post提交纯文本数据功能演示
  306. httpplus_app_post_text()
  307. -- http post提交xml数据功能演示
  308. httpplus_app_post_xml()
  309. -- http post提交原始二进制数据功能演示
  310. httpplus_app_post_binary()
  311. -- http post文件上传功能演示
  312. httpplus_app_post_file()
  313. -- 60秒之后,循环测试
  314. sys.wait(60000)
  315. end
  316. end
  317. --创建并且启动一个task
  318. --运行这个task的处理函数httpplus_app_task_func
  319. sys.taskInit(httpplus_app_task_func)