http.lua 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. --- 模块功能:HTTP客户端
  2. -- @module http
  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. -- @return string,table,string,正常返回response_code, response_header, response_body
  31. -- @return string,string,错误返回 response_code, error_message
  32. -- @usage local c, h, b = http.request(url, method, headers, body)
  33. -- @usage local r, e = http.request("http://wrong.url/ ")
  34. function request(method, url, timeout, params, data, ctype, basic, headers)
  35. local response_header, response_code, response_message, response_body, host, port, path, str, sub, len = {}
  36. local headers =
  37. headers or
  38. {
  39. ['User-Agent'] = 'Mozilla/4.0',
  40. ['Accept'] = '*/*',
  41. ['Accept-Language'] = 'zh-CN,zh,cn',
  42. ['Content-Type'] = 'application/x-www-form-urlencoded',
  43. ['Content-Length'] = '0',
  44. ['Connection'] = 'close'
  45. }
  46. -- 判断SSL支持是否满足
  47. local ssl, https = string.find(rtos.get_version(), 'SSL'), url:find('https://')
  48. if ssl == nil and https then
  49. return '401', 'SOCKET_SSL_ERROR'
  50. end
  51. -- 对host:port整形
  52. if url:find('://') then
  53. url = url:sub(8)
  54. end
  55. sub = url:find('/')
  56. if not sub then
  57. url = url .. '/'
  58. sub = -1
  59. end
  60. str = url:match('([%w%.%-%:]+)/')
  61. port = str:match(':(%d+)') or 80
  62. host = str:match('[%w%.%-]+')
  63. path = url:sub(sub)
  64. sub = ''
  65. -- 处理查询字符串
  66. if params ~= nil and type(params) == 'table' then
  67. path = path .. '?' .. urlencodeTab(params)
  68. end
  69. -- 处理HTTP协议body部分的数据
  70. ctype = ctype or 2
  71. headers['Content-Type'] = Content_type[ctype]
  72. if ctype == 1 and data ~= nil then
  73. if type(data) == 'table' then
  74. data = table.concat(data)
  75. end
  76. sub = urlencodeTab(data)
  77. len = string.len(sub)
  78. headers['Content-Length'] = len or 0
  79. elseif ctype == 2 and data ~= nil then
  80. if type(data) == 'table' then
  81. sub = json.encode(data)
  82. elseif type(data) == 'string' then
  83. sub = data
  84. end
  85. len = string.len(sub)
  86. headers['Content-Length'] = len or 0
  87. elseif ctype == 3 and type(data) == 'string' then
  88. len = io.filesize(data)
  89. headers['Content-Length'] = len or 0
  90. end
  91. -- 处理HTTP Basic Authorization 验证
  92. if basic ~= nil and type(basic) == 'string' then
  93. headers['Authorization'] = 'Basic ' .. crypto.base64_encode(basic, #basic)
  94. end
  95. -- 处理headers部分
  96. local msg = {}
  97. for k, v in pairs(headers) do
  98. table.insert(msg, k .. ': ' .. v)
  99. end
  100. -- 合并request报文
  101. str = str .. '\r\n' .. table.concat(msg, '\r\n') .. '\r\n\r\n'
  102. -- log.info("http.request send:", str:tohex())
  103. -- 发送请求报文
  104. local c = socket.tcp()
  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\nHost: ' .. str .. sub .. '\r\n'
  111. if not c:send(str) then
  112. c:close()
  113. return '426', 'SOCKET_SEND_ERROR'
  114. end
  115. else
  116. str = method .. ' ' .. path .. ' HTTP/1.0\r\nHost: ' .. str
  117. if not c:send(str) then
  118. c:close()
  119. return '426', 'SOCKET_SEND_ERROR'
  120. end
  121. local file = io.open(data, 'r')
  122. if file then
  123. while true do
  124. local dat = file:read(1460)
  125. if dat == nil then
  126. io.close(file)
  127. break
  128. end
  129. -- log.info('http.request dat:', dat:tohex())
  130. if not c:send(dat) then
  131. io.close(file)
  132. c:close()
  133. return '426', 'SOCKET_SEND_ERROR'
  134. end
  135. end
  136. end
  137. if not c:send('\r\n') then
  138. c:close()
  139. return '426', 'SOCKET_SEND_ERROR'
  140. end
  141. end
  142. msg = {}
  143. r, s = c:recv(timeout)
  144. if not r then
  145. return '503', 'SOCKET_RECV_TIMOUT'
  146. end
  147. response_code = s:match(' (%d+) ')
  148. response_message = s:match(' (%a+)')
  149. log.info('http.response code and message:\t', response_code, response_message)
  150. for k, v in s:gmatch('([%a%-]+): (%C+)') do
  151. response_header[k] = v
  152. end
  153. gzip = s:match('%aontent%-%ancoding: (%a+)')
  154. while true do
  155. table.insert(msg, s)
  156. r, s = c:recv(timeout)
  157. if not r then
  158. break
  159. end
  160. end
  161. c:close()
  162. str = table.concat(msg)
  163. sub, len = str:find('\r?\n\r?\n')
  164. if gzip then
  165. return response_code, response_header, ((zlib.inflate(table.concat(msg))):read())
  166. end
  167. return response_code, response_header, str:sub(len + 1, -1)
  168. end