httpplus.lua 25 KB

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