httpv2.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. --- 模块功能:HTTP客户端
  2. -- @module httpv2
  3. -- @author 稀饭放姜
  4. -- @license MIT
  5. -- @copyright OpenLuat.com
  6. -- @release 2017.10.23
  7. require 'socket'
  8. require 'utils'
  9. module(..., package.seeall)
  10. local Content_type = {'application/x-www-form-urlencoded', 'application/json', 'application/octet-stream'}
  11. -- 处理表的url编码
  12. function urlencodeTab(params)
  13. local msg = {}
  14. for k, v in pairs(params) do
  15. table.insert(msg, string.urlEncode(k) .. '=' .. string.urlEncode(v))
  16. table.insert(msg, '&')
  17. end
  18. table.remove(msg)
  19. return table.concat(msg)
  20. end
  21. --- HTTP客户端
  22. -- @string method,提交方式"GET" or "POST"
  23. -- @string url,HTTP请求超链接
  24. -- @number timeout,超时时间
  25. -- @param params,table类型,请求发送的查询字符串,通常为键值对表
  26. -- @param data,table类型,正文提交的body,通常为键值对、json或文件对象类似的表
  27. -- @number ctype,Content-Type的类型(可选1,2,3),默认1:"urlencode",2:"json",3:"octet-stream"
  28. -- @string basic,HTTP客户端的authorization basic验证的"username:password"
  29. -- @param headers,table类型,HTTP headers部分
  30. -- @param cert,table类型,此参数可选,默认值为: nil,ssl连接需要的证书配置,只有ssl参数为true时,才参数才有意义,cert格式如下:
  31. -- {
  32. -- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
  33. -- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
  34. -- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式) clientPassword = "123456", --客户端证书文件密码[可选]
  35. -- }
  36. -- @return string,table,string,正常返回response_code, response_header, response_body
  37. -- @return string,string,错误返回 response_code, error_message
  38. -- @usage local c, h, b = httpv2.request(url, method, headers, body)
  39. -- @usage local r, e = httpv2.request("http://wrong.url/ ")
  40. function request(method, url, timeout, params, data, ctype, basic, head, cert, fnc)
  41. local response_header, response_code, response_body = {}
  42. local _, idx, offset, ssl, auth, https, host, port, path
  43. local headers = {
  44. ['User-Agent'] = 'Mozilla/4.0',
  45. ['Accept'] = '*/*',
  46. ['Accept-Language'] = 'zh-CN,zh,cn',
  47. ['Content-Type'] = 'application/x-www-form-urlencoded',
  48. ['Connection'] = 'close',
  49. }
  50. if type(head) == "string" then
  51. log.info("user header:", basic, head)
  52. local tmp = {}
  53. for k, v in string.gmatch(head, "(.-):%s*(.-)\r\n") do tmp[k] = v end
  54. -- headers = tmp
  55. table.merge(headers, tmp)
  56. elseif type(head) == "table" then
  57. table.merge(headers, head)
  58. end
  59. -- 处理url的协议头和鉴权
  60. _, offset, https = url:find("^(%a+)://")
  61. _, idx, auth = url:find("(.-:.-)@", (offset or 0) + 1)
  62. offset = idx or offset
  63. -- 对host:port整形
  64. if url:match("^[^/]+:(%d+)", (offset or 0) + 1) then
  65. _, offset, host, port = url:find("^([^/]+):(%d+)", (offset or 0) + 1)
  66. elseif url:find("(.-)/", (offset or 0) + 1) then
  67. _, offset, host = url:find("(.-)/", (offset or 0) + 1)
  68. offset = offset - 1
  69. else
  70. offset, host = #url, url:sub((offset or 0) + 1, -1)
  71. end
  72. if not host then return '105', 'ERR_NAME_NOT_RESOLVED' end
  73. if not headers.Host then headers["Host"] = host end
  74. port = port or (https == "https" and 443 or 80)
  75. path = url:sub(offset + 1, -1)
  76. path = path == "" and "/" or path
  77. -- 处理查询字符串
  78. if params then path = path .. '?' .. (type(params) == 'table' and urlencodeTab(params) or params) end
  79. -- 处理HTTP协议body部分的数据
  80. ctype = ctype or 2
  81. headers['Content-Type'] = Content_type[ctype]
  82. if ctype == 1 and type(data) == 'table' then
  83. data = urlencodeTab(data)
  84. headers['Content-Length'] = #data or 0
  85. elseif ctype == 2 and data ~= nil then
  86. data = type(data) == 'string' and data or (type(data) == 'table' and json.encode(data)) or ""
  87. headers['Content-Length'] = #data or 0
  88. elseif ctype == 3 and type(data) == 'string' then
  89. headers['Content-Length'] = io.fileSize(data) or 0
  90. elseif data and type(data) == "string" then
  91. headers['Content-Length'] = #data or 0
  92. end
  93. -- 处理HTTP Basic Authorization 验证
  94. if auth then
  95. headers['Authorization'] = 'Basic ' .. crypto.base64_encode(auth, #auth)
  96. elseif type(basic) == 'string' and basic ~= "" then
  97. headers['Authorization'] = 'Basic ' .. crypto.base64_encode(basic, #basic)
  98. end
  99. -- 处理headers部分
  100. local str = ""
  101. for k, v in pairs(headers) do str = str .. k .. ": " .. v .. "\r\n" end
  102. -- 发送请求报文
  103. while not socket.isReady() do sys.wait(1000) end
  104. local c = socket.tcp(https == "https", cert)
  105. if not c:connect(host, port) then
  106. c:close()
  107. return '502', 'SOCKET_CONN_ERROR'
  108. end
  109. if ctype ~= 3 then
  110. str = method .. ' ' .. path .. ' HTTP/1.0\r\n' .. str .. '\r\n' .. (data and data .. "\r\n" or "")
  111. log.info("发送的http报文:", str)
  112. if not c:send(str) then
  113. c:close()
  114. return '426', 'SOCKET_SEND_ERROR'
  115. end
  116. else
  117. str = method .. ' ' .. path .. ' HTTP/1.0\r\n' .. str .. '\r\n'
  118. log.info("发送的http报文:", str)
  119. if not c:send(str) then
  120. c:close()
  121. return '426', 'SOCKET_SEND_ERROR'
  122. end
  123. local file = io.open(data, 'r')
  124. if file then
  125. while true do
  126. local dat = file:read(8192)
  127. if dat == nil then
  128. io.close(file)
  129. break
  130. end
  131. if not c:send(dat) then
  132. io.close(file)
  133. c:close()
  134. return '426', 'SOCKET_SEND_ERROR'
  135. end
  136. end
  137. end
  138. if not c:send('\r\n') then
  139. c:close()
  140. return '426', 'SOCKET_SEND_ERROR'
  141. end
  142. end
  143. ------------------------------------ 接收服务器返回消息部分 ------------------------------------
  144. local msg, str = {}, ""
  145. local r, s = c:recv(timeout)
  146. if not r then
  147. c:close()
  148. return '503', 'SOCKET_RECV_TIMOUT'
  149. end
  150. -- 处理状态代码
  151. _, idx, response_code = s:find("%s(%d+)%s.-\r\n")
  152. _, offset = s:find('\r\n\r\n')
  153. if not idx or not offset then return '501', 'SERVER_NOT_RESPONSE' end
  154. log.info('httpv2.response code:', response_code)
  155. -- 处理headers代码
  156. for k, v in string.gmatch(s:sub(idx + 1, offset), "(.-):%s*(.-)\r\n") do response_header[k] = v end
  157. local len = response_header["Content-Range"] and tonumber(response_header["Content-Range"]:match("/(%d+)")) or tonumber(response_header["Content-Length"]) or 2147483648
  158. s = s:sub((offset or 0) + 1, -1)
  159. local cnt = #s
  160. if tonumber(response_code) == 200 or tonumber(response_code) == 206 then
  161. -- 处理body
  162. while true do
  163. if type(fnc) == "function" then
  164. fnc(s, len)
  165. else
  166. table.insert(msg, s)
  167. end
  168. if cnt >= len then break end
  169. r, s = c:recv(timeout)
  170. if not r then break end
  171. cnt = cnt + #s
  172. end
  173. s = table.concat(msg) or ""
  174. end
  175. c:close()
  176. local gzip = response_header["Content-Encoding"] == "gzip"
  177. return response_code, response_header, gzip and ((zlib.inflate(s)):read()) or s
  178. end