15 -- 5. body使用zbuff返回,可直接传输给uart等库
24 local TAG
= "httpplus"
26 local function http_opts_parse(opts
)
28 log.error(TAG
, "opts不能为nil")
29 return -100, "opts不能为nil"
31 if not opts
.url
or #opts
.url
< 5 then
32 log.error(TAG
, "URL不存在或者太短了", url
)
33 return -100, "URL不存在或者太短了"
35 if not opts
.headers
then
39 if opts
.debug
or httpplus
.debug
then
45 -- log.info(TAG, "无日志")
53 if opts
.url
:startsWith("https://") then
56 elseif opts
.url
:startsWith("http://") then
61 -- log.info("http分解阶段1", is_ssl, tmp)
67 uri
= tmp
:sub((tmp
:find("/"))) -- 注意find会返回多个值
68 tmp
= tmp
:sub(1, tmp
:find("/") - 1)
72 -- log.info("http分解阶段2", is_ssl, tmp, uri)
73 if tmp
== nil or #tmp
== 0 then
74 log.error(TAG
, "非法的URL", url
)
79 local auth
= tmp
:sub(1, tmp
:find("@") - 1)
80 if not opts
.headers
["Authorization"] then
81 opts
.headers
["Authorization"] = "Basic " .. auth
:toBase64()
83 -- log.info("http鉴权信息", auth, opts.headers["Authorization"])
84 tmp
= tmp
:sub(tmp
:find("@") + 1)
88 host
= tmp
:sub(1, tmp
:find(":") - 1)
89 port
= tmp
:sub(tmp
:find(":") + 1)
94 if not port
or port
< 1 then
102 if not opts
.headers
["Host"] then
103 opts
.headers
["Host"] = string.format("%s:%d", host
, port
)
106 opts
.headers
["Connection"] = "Close"
112 -- multipart需要boundary
113 local boundary
= "------------------------16ef6e68ef" .. tostring(os
.time())
114 opts
.boundary
= boundary
119 opts
.multipart
= true
122 txt
= "text/plain", -- 文本
123 jpg
= "image/jpeg", -- JPG 格式图片
124 jpeg
= "image/jpeg", -- JPEG 格式图片
125 png
= "image/png", -- PNG 格式图片
126 gif
= "image/gif", -- GIF 格式图片
127 html
= "image/html", -- HTML
128 json
= "application/json" -- JSON
130 for kk
, vv
in pairs(opts
.files
) do
131 local ct
= contentType
[vv
:match("%.(%w+)$")] or "application/octet-stream"
132 local fname
= vv
:match("[^%/]+%w$")
133 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
)
134 -- log.info("文件传输头", tmp)
135 table.insert(opts
.mp
, {vv
, tmp
, "file"})
136 opts
.body_len
= opts
.body_len
+ #tmp
+ io
.fileSize(vv
) + 2
137 -- log.info("当前body长度", opts.body_len, "文件长度", io.fileSize(vv), fname, ct)
143 if opts
.multipart
then
144 for kk
, vv
in pairs(opts
.forms
) do
145 local tmp
= string.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n", boundary
, kk
)
146 table.insert(opts
.mp
, {vv
, tmp
, "form"})
147 opts
.body_len
= opts
.body_len
+ #tmp
+ #vv
+ 2
148 -- log.info("当前body长度", opts.body_len, "数据长度", #vv)
151 if not opts
.headers
["Content-Type"] then
152 opts
.headers
["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
154 local buff
= zbuff
.create(120)
155 for kk
, vv
in pairs(opts
.forms
) do
158 buff
:copy(nil, string.urlEncode(tostring(vv
)))
161 if buff
:used() > 0 then
164 opts
.body_len
= buff
:used()
165 opts
.log(TAG
, "普通表单", opts
.body
)
171 if opts
.multipart
then
172 -- 如果没主动设置body, 那么补个结尾
173 if not opts
.body
then
174 opts
.body_len
= opts
.body_len
+ #boundary
+ 2 + 2 + 2
176 -- Content-Type没设置? 那就设置一下
177 if not opts
.headers
["Content-Type"] then
178 opts
.headers
["Content-Type"] = "multipart/form-data; boundary="..boundary
183 if opts
.bodyfile
then
184 local fd
= io
.open(opts
.bodyfile
, "rb")
186 log.error("httpplus", "bodyfile失败,文件不存在", opts
.bodyfile
)
187 return -104, "bodyfile失败,文件不存在"
190 opts
.body_len
= io
.fileSize(opts
.bodyfile
)
194 if opts
.body
and (not opts
.body_len
or opts
.body_len
== 0) then
196 if type(opts
.body
) == "userdata" then
197 opts
.body_len
= opts
.body
:used()
199 elseif type(opts
.body
) == "table" then
200 opts
.body
= json
.encode(opts
.body
, "7f")
202 opts
.body_len
= #opts
.body
203 if not opts
.headers
["Content-Type"] then
204 opts
.headers
["Content-Type"] = "application/json;charset=UTF-8"
205 opts
.log(TAG
, "JSON", opts
.body
)
210 opts
.body
= tostring(opts
.body
)
211 opts
.body_len
= #opts
.body
214 -- 一定要设置Content-Length,而且强制覆盖客户自定义的值
215 -- opts.body_len = opts.body_len or 0
216 opts
.headers
["Content-Length"] = tostring(opts
.body_len
or 0)
219 if not opts
.method
or #opts
.method
== 0 then
220 if opts
.body_len
> 0 then
227 opts
.method
= opts
.method
:upper()
231 opts
.log(TAG
, is_ssl
, host
, port
, uri
, json
.encode(opts
.headers
))
241 if not opts
.timeout
or opts
.timeout
== 0 then
245 return -- 成功完成,不需要返回值
250 local function zbuff_find(buff
, str
)
251 -- log.info("zbuff查找", buff:used(), #str)
252 if buff
:used() < #str
then
255 local maxoff
= buff
:used()
256 maxoff
= maxoff
- #str
257 local tmp
= zbuff
.create(#str
)
259 -- log.info("tmp数据", tmp:query():toHex())
260 for i
= 0, maxoff
, 1 do
262 for j
= 0, #str
- 1, 1 do
263 -- log.info("对比", i, j, string.char(buff[i+j]):toHex(), string.char(tmp[j]):toHex(), buff[i+j] ~= tmp[j])
264 if buff
[i
+j
] ~= tmp
[j
] then
275 local function resp_parse(opts
)
276 -- log.info("这里--------")
277 local header_offset
= zbuff_find(opts
.rx_buff
, "\r\n\r\n")
278 -- log.info("头部偏移量", header_offset)
279 if not header_offset
then
280 log.warn(TAG
, "没有检测到http响应头部,非法响应")
281 opts
.resp_code
= -198
284 local state_line_offset
= zbuff_find(opts
.rx_buff
, "\r\n")
285 local state_line
= opts
.rx_buff
:query(0, state_line_offset
)
286 local tmp
= state_line
:split(" ")
287 if not tmp
or #tmp
< 2 then
288 log.warn(TAG
, "非法的响应行", state_line
)
289 opts
.resp_code
= -197
292 local code
= tonumber(tmp
[2])
294 log.warn(TAG
, "非法的响应码", tmp
[2])
295 opts
.resp_code
= -196
298 opts
.resp_code
= code
302 opts
.log(TAG
, "state code", code
)
303 -- TODO 解析header和body
305 opts
.rx_buff
:del(0, state_line_offset
+ 2)
306 -- opts.log(TAG, "剩余的响应体", opts.rx_buff:query())
310 local offset
= zbuff_find(opts
.rx_buff
, "\r\n")
312 log.warn(TAG
, "不合法的剩余headers", opts
.rx_buff
:query())
317 opts
.rx_buff
:del(0, 2)
320 local line
= opts
.rx_buff
:query(0, offset
)
321 opts
.rx_buff
:del(0, offset
+ 2)
322 local tmp2
= line
:split(":")
323 opts
.log(TAG
, tmp2
[1]:trim(), tmp2
[2]:trim())
324 opts
.resp
.headers
[tmp2
[1]:trim()] = tmp2
[2]:trim()
327 -- if opts.resp_code < 299 then
329 -- 有Content-Length就好办
330 if opts
.resp
.headers
["Content-Length"] then
331 opts
.log(TAG
, "有长度, 标准的咯")
332 opts
.resp
.body
= opts
.rx_buff
333 elseif opts
.resp
.headers
["Transfer-Encoding"] == "chunked" then
334 -- log.info(TAG, "数据是chunked编码", opts.rx_buff[0], opts.rx_buff[1])
335 -- log.info(TAG, "数据是chunked编码", opts.rx_buff:query(0, 4):toHex())
338 while crun
and coffset
< opts
.rx_buff
:used() do
339 -- 从当前offset读取长度, 长度总不会超过8字节吧?
341 -- local coffset = zbuff_find(opts.rx_buff, "\r\n")
342 -- if not coffset then
346 if opts
.rx_buff
[coffset
+i
] == 0x0D and opts
.rx_buff
[coffset
+i
+1] == 0x0A then
347 local ctmp
= opts
.rx_buff
:query(coffset
, i
)
348 -- opts.log(TAG, "chunked分片长度", ctmp, ctmp:toHex())
349 local clen
= tonumber(ctmp
, 16)
350 -- opts.log(TAG, "chunked分片长度2", clen)
353 opts
.rx_buff
:resize(coffset
)
357 opts
.rx_buff
:del(coffset
, i
+2)
358 coffset
= coffset
+ clen
366 log.error("非法的chunked块")
370 opts
.resp
.body
= opts
.rx_buff
381 local function http_socket_cb(opts
, event
)
382 opts
.log(TAG
, "tcp.event", event
)
383 if event
== socket
.ON_LINE
then
385 -- opts.state = "ON_LINE"
386 sys
.publish(opts
.topic
)
387 elseif event
== socket
.TX_OK
then
388 -- 数据传输完成, 如果是文件上传就需要这个消息
389 -- opts.state = "TX_OK"
390 sys
.publish(opts
.topic
)
391 elseif event
== socket
.EVENT
then
392 -- 收到数据或者链接断开了, 这里总需要读取一次才知道
393 local succ
, data_len
= socket
.rx(opts
.netc
, opts
.rx_buff
)
394 if succ
and data_len
> 0 then
395 opts
.log(TAG
, "收到数据", data_len
, "总长", #opts
.rx_buff
)
396 -- opts.log(TAG, "数据", opts.rx_buff:query())
398 if not opts
.is_closed
then
399 opts
.log(TAG
, "服务器已经断开了连接或接收出错")
400 opts
.is_closed
= true
401 sys
.publish(opts
.topic
)
404 elseif event
== socket
.CLOSED
then
405 log.info(TAG
, "连接已关闭")
406 opts
.is_closed
= true
407 sys
.publish(opts
.topic
)
411 local function http_exec(opts
)
412 local netc
= socket
.create(opts
.adapter
, function(sc
, event
)
414 return http_socket_cb(opts
, event
)
418 log.error(TAG
, "创建socket失败了!!")
422 opts
.rx_buff
= zbuff
.create(1024)
423 opts
.topic
= tostring(netc
)
424 socket
.config(netc
, nil,nil, opts
.is_ssl
)
425 if opts
.debug
or httpplus
.debug
then
428 if not socket
.connect(netc
, opts
.host
, opts
.port
, opts
.try_ipv6
) then
429 log.warn(TAG
, "调用socket.connect返回错误了")
430 return -103, "调用socket.connect返回错误了"
432 local ret
= sys
.waitUntil(opts
.topic
, 5000)
434 log.warn(TAG
, "建立连接超时了!!!")
435 return -104, "建立连接超时了!!!"
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
)
447 socket
.tx(netc
, line
)
451 local write_counter
= 0
452 if opts
.mp
and #opts
.mp
> 0 then
453 opts
.log(TAG
, "执行mulitpart上传模式")
454 for k
, v
in pairs(opts
.mp
) do
455 socket
.tx(netc
, v
[2])
456 write_counter
= write_counter
+ #v
[2]
457 if v
[3] == "file" then
458 -- log.info("写入文件数据头", v[2])
459 local fd
= io
.open(v
[1], "rb")
460 -- log.info("写入文件数据", v[1])
462 while not opts
.is_closed
do
463 local fdata
= fd
:read(1400)
464 if not fdata
or #fdata
== 0 then
467 -- log.info("写入文件数据", "长度", #fdata)
468 socket
.tx(netc
, fdata
)
469 write_counter
= write_counter
+ #fdata
471 sys
.waitUntil(opts
.topic
, 3000)
476 socket
.tx(netc
, v
[1])
477 write_counter
= write_counter
+ #v
[1]
479 socket
.tx(netc
, "\r\n")
480 write_counter
= write_counter
+ 2
482 -- rbody = rbody .. "--" .. opts.boundary .. "--\r\n"
483 socket
.tx(netc
, "--")
484 socket
.tx(netc
, opts
.boundary
)
485 socket
.tx(netc
, "--\r\n")
486 write_counter
= write_counter
+ #opts
.boundary
+ 2 + 2 + 2
487 elseif opts
.bodyfile
then
488 local fd
= io
.open(opts
.bodyfile
, "rb")
489 -- log.info("写入文件数据", v[1])
491 while not opts
.is_closed
do
492 local fdata
= fd
:read(1400)
493 if not fdata
or #fdata
== 0 then
496 -- log.info("写入文件数据", "长度", #fdata)
497 socket
.tx(netc
, fdata
)
498 write_counter
= write_counter
+ #fdata
500 sys
.waitUntil(opts
.topic
, 300)
504 elseif opts
.body
then
505 if type(opts
.body
) == "string" and #opts
.body
> 0 then
506 socket
.tx(netc
, opts
.body
)
507 write_counter
= write_counter
+ #opts
.body
508 elseif type(opts
.body
) == "userdata" then
509 write_counter
= write_counter
+ opts
.body
:used()
510 if opts
.body
:used() < 4*1024 then
511 socket
.tx(netc
, opts
.body
)
514 local tmpbuff
= opts
.body
515 local tsize
= tmpbuff
:used()
516 while offset
< tsize
do
517 opts
.log(TAG
, "body(zbuff)分段写入", offset
, tsize
)
518 if tsize
- offset
> 4096 then
519 socket
.tx(netc
, tmpbuff
:toStr(offset
, 4096))
520 offset
= offset
+ 4096
521 sys
.waitUntil(opts
.topic
, 300)
523 socket
.tx(netc
, tmpbuff
:toStr(offset
, tsize
- offset
))
530 -- log.info("写入长度", "期望", opts.body_len, "实际", write_counter)
531 -- log.info("hex", rbody)
534 while not opts
.is_closed
and opts
.timeout
> 0 do
535 log.info(TAG
, "等待服务器完成响应")
536 sys
.waitUntil(opts
.topic
, 1000)
537 opts
.timeout
= opts
.timeout
- 1
539 log.info(TAG
, "服务器已完成响应,开始解析响应")
541 -- log.info("执行完成", "返回结果")
546 @api httpplus.request(opts)
547 @table 请求参数,是一个table,最起码得有url属性
548 @return int 响应码,服务器返回的状态码>=100, 若本地检测到错误,会返回<0的值
549 @return 服务器正常响应时返回结果, 否则是错误信息或者nil
553 url = "https://httpbin.air32.cn/abc", -- 必选, 目标URL
554 method = "POST", -- 可选,默认GET, 如果有body,files,forms参数,会设置成POST
555 headers = {}, -- 可选,自定义的额外header
556 files = {}, -- 可选,文件上传,若存在本参数,会强制以multipart/form-data形式上传
557 forms = {}, -- 可选,表单参数,若存在本参数,如果不存在files,按application/x-www-form-urlencoded上传
558 body = "abc=123",-- 可选,自定义body参数, 字符串/zbuff/table均可, 但不能与files和forms同时存在
559 debug = false, -- 可选,打开调试日志,默认false
560 try_ipv6 = false, -- 可选,是否优先尝试ipv6地址,默认是false
561 adapter = nil, -- 可选,网络适配器编号, 默认是自动选
562 timeout = 30, -- 可选,读取服务器响应的超时时间,单位秒,默认30
563 bodyfile = "xxx" -- 可选,直接把文件内容作为body上传, 优先级高于body参数
566 local code, resp = httpplus.request({url="https://httpbin.air32.cn/get"})
567 log.info("http", code)
569 -- 情况1, code >= 100 时, resp会是个table, 包含2个元素
572 log.info("http", "headers", json.encode(resp.headers))
574 -- 通过query函数可以转为lua的string
575 log.info("http", "headers", resp.body:query())
576 -- 也可以通过uart.tx等支持zbuff的函数转发出去
577 -- uart.tx(1, resp.body)
580 function httpplus
.request(opts
)
582 local ret
= http_opts_parse(opts
)
588 local ret
, msg
= pcall(http_exec
, opts
)
591 if not opts
.is_closed
then
592 socket
.close(opts
.netc
)
594 socket
.release(opts
.netc
)
602 return opts
.resp_code
, opts
.resp