httpplus.lua 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. --[[
  2. @module httpplus
  3. @summary http库的补充
  4. @version 1.0
  5. @date 2023.11.23
  6. @author wendal
  7. @demo httpplus
  8. @tag LUAT_USE_NETWORK
  9. @usage
  10. -- 本库支持的功能有:
  11. -- 1. 大文件上传的问题,不限大小
  12. -- 2. 任意长度的header设置
  13. -- 3. 任意长度的body设置
  14. -- 4. 鉴权URL自动识别
  15. -- 5. body使用zbuff返回,可直接传输给uart等库
  16. -- 与http库的差异
  17. -- 1. 不支持文件下载
  18. -- 2. 不支持fota
  19. ]]
  20. local httpplus = {}
  21. local TAG = "httpplus"
  22. local function http_opts_parse(opts)
  23. if not opts then
  24. log.error(TAG, "opts不能为nil")
  25. return -100, "opts不能为nil"
  26. end
  27. if not opts.url or #opts.url < 5 then
  28. log.error(TAG, "URL不存在或者太短了", url)
  29. return -100, "URL不存在或者太短了"
  30. end
  31. if not opts.headers then
  32. opts.headers = {}
  33. end
  34. if opts.debug or httpplus.debug then
  35. if not opts.log then
  36. opts.log = log.debug
  37. end
  38. else
  39. opts.log = function()
  40. -- log.info(TAG, "无日志")
  41. end
  42. end
  43. -- 解析url
  44. -- 先判断协议是否加密
  45. local is_ssl = false
  46. local tmp = ""
  47. if opts.url:startsWith("https://") then
  48. is_ssl = true
  49. tmp = opts.url:sub(9)
  50. elseif opts.url:startsWith("http://") then
  51. tmp = opts.url:sub(8)
  52. else
  53. tmp = opts.url
  54. end
  55. -- log.info("http分解阶段1", is_ssl, tmp)
  56. -- 然后判断host段
  57. local uri = ""
  58. local host = ""
  59. local port = 0
  60. if tmp:find("/") then
  61. uri = tmp:sub((tmp:find("/"))) -- 注意find会返回多个值
  62. tmp = tmp:sub(1, tmp:find("/") - 1)
  63. else
  64. uri = "/"
  65. end
  66. -- log.info("http分解阶段2", is_ssl, tmp, uri)
  67. if tmp == nil or #tmp == 0 then
  68. log.error(TAG, "非法的URL", url)
  69. return -101, "非法的URL"
  70. end
  71. -- 有无鉴权信息
  72. if tmp:find("@") then
  73. local auth = tmp:sub(1, tmp:find("@") - 1)
  74. if not opts.headers["Authorization"] then
  75. opts.headers["Authorization"] = "Basic " .. auth:toBase64()
  76. end
  77. -- log.info("http鉴权信息", auth, opts.headers["Authorization"])
  78. tmp = tmp:sub(tmp:find("@") + 1)
  79. end
  80. -- 解析端口
  81. if tmp:find(":") then
  82. host = tmp:sub(1, tmp:find(":") - 1)
  83. port = tmp:sub(tmp:find(":") + 1)
  84. port = tonumber(port)
  85. else
  86. host = tmp
  87. end
  88. if not port or port < 1 then
  89. if is_ssl then
  90. port = 443
  91. else
  92. port = 80
  93. end
  94. end
  95. -- 收尾工作
  96. if not opts.headers["Host"] then
  97. opts.headers["Host"] = string.format("%s:%d", host, port)
  98. end
  99. -- Connection 必须关闭
  100. opts.headers["Connection"] = "Close"
  101. -- 复位一些变量,免得判断出错
  102. opts.is_closed = nil
  103. opts.body_len = 0
  104. -- multipart需要boundary
  105. local boundary = "------------------------16ef6e68ef" .. tostring(os.time())
  106. opts.boundary = boundary
  107. opts.mp = {}
  108. if opts.files then
  109. -- 强制设置为true
  110. opts.multipart = true
  111. end
  112. -- 表单数据
  113. if opts.forms then
  114. if opts.multipart then
  115. for kk, vv in pairs(opts.forms) do
  116. local tmp = string.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n", boundary, kk)
  117. table.insert(opts.mp, {vv, tmp, "form"})
  118. opts.body_len = opts.body_len + #tmp + #vv + 2
  119. -- log.info("当前body长度", opts.body_len, "数据长度", #vv)
  120. end
  121. else
  122. if not opts.headers["Content-Type"] then
  123. opts.headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
  124. end
  125. local buff = zbuff.create(120)
  126. for kk, vv in pairs(opts.forms) do
  127. buff:copy(nil, kk)
  128. buff:copy(nil, "=")
  129. buff:copy(nil, string.urlEncode(tostring(vv)))
  130. buff:copy(nil, "&")
  131. end
  132. if buff:used() > 0 then
  133. buff:del(-1, 1)
  134. opts.body = buff
  135. opts.body_len = buff:used()
  136. opts.log(TAG, "普通表单", opts.body)
  137. end
  138. end
  139. end
  140. if opts.files then
  141. -- 强制设置为true
  142. opts.multipart = true
  143. local contentType =
  144. {
  145. txt = "text/plain", -- 文本
  146. jpg = "image/jpeg", -- JPG 格式图片
  147. jpeg = "image/jpeg", -- JPEG 格式图片
  148. png = "image/png", -- PNG 格式图片
  149. gif = "image/gif", -- GIF 格式图片
  150. html = "image/html", -- HTML
  151. json = "application/json", -- JSON
  152. mp4 = "video/mp4", -- MP4 格式视频
  153. mp3 = "audio/mp3", -- MP3 格式音频
  154. webm = "video/webm", -- WebM 格式视频
  155. }
  156. for kk, vv in pairs(opts.files) do
  157. local ct = contentType[vv:match("%.(%w+)$")] or "application/octet-stream"
  158. local fname = vv:match("[^%/]+%w$")
  159. local tmp = string.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n", boundary, kk, fname, ct)
  160. -- log.info("文件传输头", tmp)
  161. table.insert(opts.mp, {vv, tmp, "file"})
  162. opts.body_len = opts.body_len + #tmp + io.fileSize(vv) + 2
  163. -- log.info("当前body长度", opts.body_len, "文件长度", io.fileSize(vv), fname, ct)
  164. end
  165. end
  166. -- 如果multipart模式
  167. if opts.multipart then
  168. -- 如果没主动设置body, 那么补个结尾
  169. if not opts.body then
  170. opts.body_len = opts.body_len + #boundary + 2 + 2 + 2
  171. end
  172. -- Content-Type没设置? 那就设置一下
  173. if not opts.headers["Content-Type"] then
  174. opts.headers["Content-Type"] = "multipart/form-data; boundary="..boundary
  175. end
  176. end
  177. -- 直接设置bodyfile
  178. if opts.bodyfile then
  179. local fd = io.open(opts.bodyfile, "rb")
  180. if not fd then
  181. log.error("httpplus", "bodyfile失败,文件不存在", opts.bodyfile)
  182. return -104, "bodyfile失败,文件不存在"
  183. end
  184. fd:close()
  185. opts.body_len = io.fileSize(opts.bodyfile)
  186. end
  187. -- 有设置body, 而且没设置长度
  188. if opts.body and (not opts.body_len or opts.body_len == 0) then
  189. -- body是zbuff的情况
  190. if type(opts.body) == "userdata" then
  191. opts.body_len = opts.body:used()
  192. -- body是json的情况
  193. elseif type(opts.body) == "table" then
  194. opts.body = json.encode(opts.body, "7f")
  195. if opts.body then
  196. opts.body_len = #opts.body
  197. if not opts.headers["Content-Type"] then
  198. opts.headers["Content-Type"] = "application/json;charset=UTF-8"
  199. opts.log(TAG, "JSON", opts.body)
  200. end
  201. end
  202. -- 其他情况就只能当文本了
  203. else
  204. opts.body = tostring(opts.body)
  205. opts.body_len = #opts.body
  206. end
  207. end
  208. -- 一定要设置Content-Length,而且强制覆盖客户自定义的值
  209. -- opts.body_len = opts.body_len or 0
  210. opts.headers["Content-Length"] = tostring(opts.body_len or 0)
  211. -- 如果没设置method, 自动补齐
  212. if not opts.method or #opts.method == 0 then
  213. if opts.body_len > 0 then
  214. opts.method = "POST"
  215. else
  216. opts.method = "GET"
  217. end
  218. else
  219. -- 确保一定是大写字母
  220. opts.method = opts.method:upper()
  221. end
  222. if opts.debug then
  223. opts.log(TAG, is_ssl, host, port, uri, json.encode(opts.headers))
  224. end
  225. -- 把剩余的属性设置好
  226. opts.host = host
  227. opts.port = port
  228. opts.uri = uri
  229. opts.is_ssl = is_ssl
  230. if not opts.timeout or opts.timeout == 0 then
  231. opts.timeout = 30
  232. end
  233. return -- 成功完成,不需要返回值
  234. end
  235. local function zbuff_find(buff, str)
  236. -- log.info("zbuff查找", buff:used(), #str)
  237. if buff:used() < #str then
  238. return
  239. end
  240. local maxoff = buff:used()
  241. maxoff = maxoff - #str
  242. local tmp = zbuff.create(#str)
  243. tmp:write(str)
  244. -- log.info("tmp数据", tmp:query():toHex())
  245. for i = 0, maxoff, 1 do
  246. local flag = true
  247. for j = 0, #str - 1, 1 do
  248. -- log.info("对比", i, j, string.char(buff[i+j]):toHex(), string.char(tmp[j]):toHex(), buff[i+j] ~= tmp[j])
  249. if buff[i+j] ~= tmp[j] then
  250. flag = false
  251. break
  252. end
  253. end
  254. if flag then
  255. return i
  256. end
  257. end
  258. end
  259. local function resp_parse(opts)
  260. -- log.info("这里--------")
  261. local header_offset = zbuff_find(opts.rx_buff, "\r\n\r\n")
  262. -- log.info("头部偏移量", header_offset)
  263. if not header_offset then
  264. log.warn(TAG, "没有检测到http响应头部,非法响应")
  265. opts.resp_code = -198
  266. return
  267. end
  268. local state_line_offset = zbuff_find(opts.rx_buff, "\r\n")
  269. local state_line = opts.rx_buff:query(0, state_line_offset)
  270. local tmp = state_line:split(" ")
  271. if not tmp or #tmp < 2 then
  272. log.warn(TAG, "非法的响应行", state_line)
  273. opts.resp_code = -197
  274. return
  275. end
  276. local code = tonumber(tmp[2])
  277. if not code then
  278. log.warn(TAG, "非法的响应码", tmp[2])
  279. opts.resp_code = -196
  280. return
  281. end
  282. opts.resp_code = code
  283. opts.resp = {
  284. headers = {}
  285. }
  286. opts.log(TAG, "state code", code)
  287. -- TODO 解析header和body
  288. opts.rx_buff:del(0, state_line_offset + 2)
  289. -- opts.log(TAG, "剩余的响应体", opts.rx_buff:query())
  290. -- 解析headers
  291. while 1 do
  292. local offset = zbuff_find(opts.rx_buff, "\r\n")
  293. if not offset then
  294. log.warn(TAG, "不合法的剩余headers", opts.rx_buff:query())
  295. break
  296. end
  297. if offset == 0 then
  298. -- header的最后一个空行
  299. opts.rx_buff:del(0, 2)
  300. break
  301. end
  302. local line = opts.rx_buff:query(0, offset)
  303. opts.rx_buff:del(0, offset + 2)
  304. local tmp2 = line:split(":")
  305. opts.log(TAG, tmp2[1]:trim(), tmp2[2]:trim())
  306. opts.resp.headers[tmp2[1]:trim()] = tmp2[2]:trim()
  307. end
  308. -- if opts.resp_code < 299 then
  309. -- 解析body
  310. -- 有Content-Length就好办
  311. if opts.resp.headers["Content-Length"] then
  312. opts.log(TAG, "有长度, 标准的咯")
  313. opts.resp.body = opts.rx_buff
  314. elseif opts.resp.headers["Transfer-Encoding"] == "chunked" then
  315. -- log.info(TAG, "数据是chunked编码", opts.rx_buff[0], opts.rx_buff[1])
  316. -- log.info(TAG, "数据是chunked编码", opts.rx_buff:query(0, 4):toHex())
  317. local coffset = 0
  318. local crun = true
  319. while crun and coffset < opts.rx_buff:used() do
  320. -- 从当前offset读取长度, 长度总不会超过8字节吧?
  321. local flag = true
  322. -- local coffset = zbuff_find(opts.rx_buff, "\r\n")
  323. -- if not coffset then
  324. -- end
  325. for i = 1, 8, 1 do
  326. if opts.rx_buff[coffset+i] == 0x0D and opts.rx_buff[coffset+i+1] == 0x0A then
  327. local ctmp = opts.rx_buff:query(coffset, i)
  328. -- opts.log(TAG, "chunked分片长度", ctmp, ctmp:toHex())
  329. local clen = tonumber(ctmp, 16)
  330. -- opts.log(TAG, "chunked分片长度2", clen)
  331. if clen == nil or clen == 0 then
  332. -- 末尾了
  333. opts.rx_buff:resize(coffset)
  334. crun = false
  335. else
  336. -- 先删除chunked块
  337. opts.rx_buff:del(coffset, i+2)
  338. coffset = coffset + clen
  339. end
  340. flag = false
  341. break
  342. end
  343. end
  344. -- 肯定能搜到chunked
  345. if flag then
  346. log.error("非法的chunked块")
  347. break
  348. end
  349. end
  350. opts.resp.body = opts.rx_buff
  351. end
  352. -- end
  353. -- 清空rx_buff
  354. opts.rx_buff = nil
  355. -- 完结散花
  356. end
  357. local function write_and_wait(opts, data, ctx)
  358. time_now = mcu.ticks()
  359. socket.tx(opts.netc, data)
  360. speed_limit = ctx.speed_limit
  361. if speed_limit and speed_limit > 0 then
  362. -- log.info("httpplus", "限速1", ctx.sent_len, ctx.data_len, mcu.hz(), speed_limit)
  363. ctx.sent_len = ctx.sent_len + ctx.data_len
  364. -- log.info("httpplus", "限速2", ctx.sent_len, mcu.ticks() - ctx.time_start, mcu.hz())
  365. local ticks_used = mcu.ticks() - ctx.time_start
  366. -- log.info("httpplus", "限速4", ticks_used, mcu.hz(), type(mcu.hz()))
  367. -- log.info("httpplus", "限速5", ticks_used, mcu.hz(), ticks_used // mcu.hz())
  368. local time_used = (mcu.ticks() - ctx.time_start + (mcu.hz() - 1)) * ( 1000 // mcu.hz())
  369. local time_used_expect = ((ctx.sent_len) // speed_limit * 1000)
  370. -- log.info("httpplus", "限速3", ctx.sent_len, time_used, time_used_expect)
  371. if time_used < time_used_expect and time_used_expect - time_used > 10 then
  372. time_wait = time_used_expect - time_used
  373. if time_wait > 1000 then
  374. time_wait = 1000
  375. end
  376. opts.log(TAG, "限速等待", time_wait, "ms")
  377. sys.waitUntil(opts.topic, time_wait)
  378. end
  379. end
  380. end
  381. -- socket 回调函数
  382. local function http_socket_cb(opts, event)
  383. opts.log(TAG, "tcp.event", event)
  384. if event == socket.ON_LINE then
  385. -- TCP链接已建立, 那就可以上行了
  386. -- opts.state = "ON_LINE"
  387. sys.publish(opts.topic)
  388. elseif event == socket.TX_OK then
  389. -- 数据传输完成, 如果是文件上传就需要这个消息
  390. -- opts.state = "TX_OK"
  391. sys.publish(opts.topic)
  392. elseif event == socket.EVENT then
  393. -- 收到数据或者链接断开了, 这里总需要读取一次才知道
  394. local succ, data_len = socket.rx(opts.netc, opts.rx_buff)
  395. if succ and data_len and data_len > 0 then
  396. opts.log(TAG, "收到数据", data_len, "总长", #opts.rx_buff)
  397. -- opts.log(TAG, "数据", opts.rx_buff:query())
  398. else
  399. if not opts.is_closed then
  400. opts.log(TAG, "服务器已经断开了连接或接收出错")
  401. opts.is_closed = true
  402. sys.publish(opts.topic)
  403. end
  404. end
  405. elseif event == socket.CLOSED then
  406. log.info(TAG, "连接已关闭")
  407. opts.is_closed = true
  408. sys.publish(opts.topic)
  409. end
  410. end
  411. local function http_exec(opts)
  412. local netc = socket.create(opts.adapter, function(sc, event)
  413. if opts.netc then
  414. return http_socket_cb(opts, event)
  415. end
  416. end)
  417. if not netc then
  418. log.error(TAG, "创建socket失败了!!")
  419. return -102
  420. end
  421. local speed_ctx = {}
  422. opts.netc = netc
  423. opts.rx_buff = zbuff.create(1024)
  424. opts.topic = tostring(netc)
  425. socket.config(netc, nil,nil, opts.is_ssl)
  426. if opts.debug or httpplus.debug then
  427. socket.debug(netc)
  428. end
  429. if not socket.connect(netc, opts.host, opts.port, opts.try_ipv6) then
  430. log.warn(TAG, "调用socket.connect返回错误了")
  431. return -103, "调用socket.connect返回错误了"
  432. end
  433. local ret = sys.waitUntil(opts.topic, 5000)
  434. if ret == false then
  435. log.warn(TAG, "建立连接超时了!!!")
  436. return -104, "建立连接超时了!!!"
  437. end
  438. -- 首先是头部
  439. local line = string.format("%s %s HTTP/1.1\r\n", opts.method:upper(), opts.uri)
  440. -- opts.log(TAG, line)
  441. socket.tx(netc, line)
  442. for k, v in pairs(opts.headers) do
  443. line = string.format("%s: %s\r\n", k, v)
  444. socket.tx(netc, line)
  445. end
  446. line = "\r\n"
  447. socket.tx(netc, line)
  448. -- 然后是body
  449. local rbody = ""
  450. local write_counter = 0
  451. local fbuf = nil
  452. local psram = rtos.meminfo("psram")
  453. local speed_limit = opts.speed_limit
  454. if psram and psram > 1024*1024 then
  455. fbuf = zbuff.create(1024 * 64, 0, zbuff.HEAP_PSRAM)
  456. else
  457. fbuf = zbuff.create(1024 * 8)
  458. end
  459. if fbuf == nil then
  460. fbuf = zbuff.create(1024 * 4)
  461. end
  462. opts.log(TAG, "上传缓冲区大小", fbuf:len())
  463. if speed_limit == nil then
  464. if hmeta and (hmeta.model() == "Air8101" or hmeta.model() == "Air8101A") then
  465. if (opts.adapter or socket.dft() or 0) == socket.LWIP_STA then
  466. -- 限制上传速度
  467. speed_limit = 48 * 1024
  468. end
  469. end
  470. end
  471. if speed_limit and speed_limit > 0 then
  472. opts.log(TAG, "启用上传限速", speed_limit)
  473. speed_ctx.speed_limit = speed_limit
  474. speed_ctx.time_start = mcu.ticks()
  475. end
  476. if opts.mp and #opts.mp > 0 then
  477. opts.log(TAG, "执行mulitpart上传模式")
  478. for k, v in pairs(opts.mp) do
  479. socket.tx(netc, v[2])
  480. write_counter = write_counter + #v[2]
  481. if v[3] == "file" then
  482. -- log.info("写入文件数据头", v[2])
  483. local fd = io.open(v[1], "rb")
  484. -- log.info("写入文件数据", v[1])
  485. if fd then
  486. speed_ctx.time_start = mcu.ticks()
  487. speed_ctx.sent_len = 0
  488. while not opts.is_closed do
  489. fbuf:seek(0)
  490. local ok, flen = fd:fill(fbuf)
  491. if not ok or flen <= 0 then
  492. break
  493. end
  494. fbuf:seek(flen)
  495. -- log.info("写入文件数据", "长度", #fdata)
  496. speed_ctx.data_len = fbuf:len()
  497. write_and_wait(opts, fbuf, speed_ctx)
  498. -- socket.tx(netc, fbuf)
  499. write_counter = write_counter + flen
  500. -- 注意, 这里要等待TX_OK事件
  501. -- sys.waitUntil(opts.topic, 300)
  502. end
  503. fd:close()
  504. end
  505. else
  506. socket.tx(netc, v[1])
  507. write_counter = write_counter + #v[1]
  508. end
  509. socket.tx(netc, "\r\n")
  510. write_counter = write_counter + 2
  511. end
  512. -- rbody = rbody .. "--" .. opts.boundary .. "--\r\n"
  513. socket.tx(netc, "--")
  514. socket.tx(netc, opts.boundary)
  515. socket.tx(netc, "--\r\n")
  516. write_counter = write_counter + #opts.boundary + 2 + 2 + 2
  517. elseif opts.bodyfile then
  518. local fd = io.open(opts.bodyfile, "rb")
  519. -- log.info("写入文件数据", v[1])
  520. if fd then
  521. speed_ctx.time_start = mcu.ticks()
  522. speed_ctx.sent_len = write_counter
  523. while not opts.is_closed do
  524. fbuf:seek(0)
  525. local ok, flen = fd:fill(fbuf)
  526. if not ok or flen <= 0 then
  527. break
  528. end
  529. fbuf:seek(flen)
  530. -- log.info("写入文件数据", "长度", #fdata)
  531. speed_ctx.data_len = flen
  532. write_and_wait(opts, fbuf, speed_ctx)
  533. -- socket.tx(netc, fbuf)
  534. write_counter = write_counter + flen
  535. -- 注意, 这里要等待TX_OK事件
  536. -- sys.waitUntil(opts.topic, 300)
  537. end
  538. fd:close()
  539. end
  540. elseif opts.body then
  541. if type(opts.body) == "string" and #opts.body > 0 then
  542. socket.tx(netc, opts.body)
  543. write_counter = write_counter + #opts.body
  544. elseif type(opts.body) == "userdata" then
  545. write_counter = write_counter + opts.body:used()
  546. if opts.body:used() < 4*1024 then
  547. socket.tx(netc, opts.body)
  548. else
  549. local offset = 0
  550. local tmpbuff = opts.body
  551. local tsize = tmpbuff:used()
  552. speed_ctx.time_start = mcu.ticks()
  553. speed_ctx.sent_len = write_counter
  554. while offset < tsize do
  555. opts.log(TAG, "body(zbuff)分段写入", offset, tsize)
  556. fbuf:seek(0)
  557. if tsize - offset > fbuf:len() then
  558. fbuf:copy(0, tmpbuff, offset, fbuf:len())
  559. offset = offset + fbuf:len()
  560. else
  561. fbuf:copy(0, tmpbuff, offset, tsize - offset)
  562. offset = tsize
  563. end
  564. write_and_wait(opts, fbuf, speed_ctx)
  565. -- socket.tx(netc, fbuf)
  566. -- sys.waitUntil(opts.topic, 300)
  567. end
  568. end
  569. end
  570. end
  571. -- log.info("写入长度", "期望", opts.body_len, "实际", write_counter)
  572. -- log.info("hex", rbody)
  573. -- 处理响应信息
  574. while not opts.is_closed and opts.timeout > 0 do
  575. log.info(TAG, "等待服务器完成响应")
  576. sys.waitUntil(opts.topic, 1000)
  577. opts.timeout = opts.timeout - 1
  578. end
  579. log.info(TAG, "服务器已完成响应,开始解析响应")
  580. resp_parse(opts)
  581. -- log.info("执行完成", "返回结果")
  582. end
  583. --[[
  584. 执行HTTP请求
  585. @api httpplus.request(opts)
  586. @table 请求参数,是一个table,最起码得有url属性
  587. @return int 响应码,服务器返回的状态码>=100, 若本地检测到错误,会返回<0的值
  588. @return 服务器正常响应时返回结果, 否则是错误信息或者nil
  589. @usage
  590. -- 请求参数介绍
  591. local opts = {
  592. url = "https://httpbin.air32.cn/abc", -- 必选, 目标URL
  593. method = "POST", -- 可选,默认GET, 如果有body,files,forms参数,会设置成POST
  594. headers = {}, -- 可选,自定义的额外header
  595. files = {}, -- 可选,键值对的形式,文件上传,若存在本参数,会强制以multipart/form-data形式上传
  596. forms = {}, -- 可选,键值对的形式,表单参数,若存在本参数,如果不存在files,按application/x-www-form-urlencoded上传
  597. body = "abc=123",-- 可选,自定义body参数, 字符串/zbuff/table均可, 但不能与files和forms同时存在
  598. debug = false, -- 可选,打开调试日志,默认false
  599. try_ipv6 = false, -- 可选,是否优先尝试ipv6地址,默认是false
  600. adapter = nil, -- 可选,网络适配器编号, 默认是自动选
  601. timeout = 30, -- 可选,读取服务器响应的超时时间,单位秒,默认30
  602. bodyfile = "xxx" -- 可选,直接把文件内容作为body上传, 优先级高于body参数
  603. }
  604. local code, resp = httpplus.request({url="https://httpbin.air32.cn/get"})
  605. log.info("http", code)
  606. -- 返回值resp的说明
  607. -- 情况1, code >= 100 时, resp会是个table, 包含2个元素
  608. if code >= 100 then
  609. -- headers, 是个table
  610. log.info("http", "headers", json.encode(resp.headers))
  611. -- body, 是个zbuff
  612. -- 通过query函数可以转为lua的string
  613. log.info("http", "headers", resp.body:query())
  614. -- 也可以通过uart.tx等支持zbuff的函数转发出去
  615. -- uart.tx(1, resp.body)
  616. end
  617. ]]
  618. function httpplus.request(opts)
  619. -- 参数解析
  620. local ret = http_opts_parse(opts)
  621. if ret then
  622. return ret
  623. end
  624. -- 执行请求
  625. local ret, msg = pcall(http_exec, opts)
  626. if opts.netc then
  627. -- 清理连接
  628. if not opts.is_closed then
  629. socket.close(opts.netc)
  630. end
  631. socket.release(opts.netc)
  632. opts.netc = nil
  633. end
  634. -- 处理响应或错误
  635. if not ret then
  636. log.error(TAG, msg)
  637. return -199, msg
  638. end
  639. return opts.resp_code, opts.resp
  640. end
  641. return httpplus