httpc.lua 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. local skynet = require "skynet"
  2. local socket = require "http.sockethelper"
  3. local url = require "http.url"
  4. local internal = require "http.internal"
  5. local dns = require "skynet.dns"
  6. local string = string
  7. local table = table
  8. local httpc = {}
  9. local function request(fd, method, host, url, recvheader, header, content)
  10. local read = socket.readfunc(fd)
  11. local write = socket.writefunc(fd)
  12. local header_content = ""
  13. if header then
  14. if not header.host then
  15. header.host = host
  16. end
  17. for k,v in pairs(header) do
  18. header_content = string.format("%s%s:%s\r\n", header_content, k, v)
  19. end
  20. else
  21. header_content = string.format("host:%s\r\n",host)
  22. end
  23. if content then
  24. local data = string.format("%s %s HTTP/1.1\r\n%scontent-length:%d\r\n\r\n", method, url, header_content, #content)
  25. write(data)
  26. write(content)
  27. else
  28. local request_header = string.format("%s %s HTTP/1.1\r\n%scontent-length:0\r\n\r\n", method, url, header_content)
  29. write(request_header)
  30. end
  31. local tmpline = {}
  32. local body = internal.recvheader(read, tmpline, "")
  33. if not body then
  34. error(socket.socket_error)
  35. end
  36. local statusline = tmpline[1]
  37. local code, info = statusline:match "HTTP/[%d%.]+%s+([%d]+)%s+(.*)$"
  38. code = assert(tonumber(code))
  39. local header = internal.parseheader(tmpline,2,recvheader or {})
  40. if not header then
  41. error("Invalid HTTP response header")
  42. end
  43. local length = header["content-length"]
  44. if length then
  45. length = tonumber(length)
  46. end
  47. local mode = header["transfer-encoding"]
  48. if mode then
  49. if mode ~= "identity" and mode ~= "chunked" then
  50. error ("Unsupport transfer-encoding")
  51. end
  52. end
  53. if mode == "chunked" then
  54. body, header = internal.recvchunkedbody(read, nil, header, body)
  55. if not body then
  56. error("Invalid response body")
  57. end
  58. else
  59. -- identity mode
  60. if length then
  61. if #body >= length then
  62. body = body:sub(1,length)
  63. else
  64. local padding = read(length - #body)
  65. body = body .. padding
  66. end
  67. else
  68. -- no content-length, read all
  69. body = body .. socket.readall(fd)
  70. end
  71. end
  72. return code, body
  73. end
  74. local async_dns
  75. function httpc.dns(server,port)
  76. async_dns = true
  77. dns.server(server,port)
  78. end
  79. function httpc.request(method, host, url, recvheader, header, content)
  80. local timeout = httpc.timeout -- get httpc.timeout before any blocked api
  81. local hostname, port = host:match"([^:]+):?(%d*)$"
  82. if port == "" then
  83. port = 80
  84. else
  85. port = tonumber(port)
  86. end
  87. if async_dns and not hostname:match(".*%d+$") then
  88. hostname = dns.resolve(hostname)
  89. end
  90. local fd = socket.connect(hostname, port, timeout)
  91. if not fd then
  92. error(string.format("http connect error host:%s, port:%s, timeout:%s", hostname, port, timeout))
  93. return
  94. end
  95. local finish
  96. if timeout then
  97. skynet.timeout(timeout, function()
  98. if not finish then
  99. socket.shutdown(fd) -- shutdown the socket fd, need close later.
  100. end
  101. end)
  102. end
  103. local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content)
  104. finish = true
  105. socket.close(fd)
  106. if ok then
  107. return statuscode, body
  108. else
  109. error(statuscode)
  110. end
  111. end
  112. function httpc.get(...)
  113. return httpc.request("GET", ...)
  114. end
  115. local function escape(s)
  116. return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
  117. return string.format("%%%02X", string.byte(c))
  118. end))
  119. end
  120. function httpc.post(host, url, form, recvheader)
  121. local header = {
  122. ["content-type"] = "application/x-www-form-urlencoded"
  123. }
  124. local body = {}
  125. for k,v in pairs(form) do
  126. table.insert(body, string.format("%s=%s",escape(k),escape(v)))
  127. end
  128. return httpc.request("POST", host, url, recvheader, header, table.concat(body , "&"))
  129. end
  130. return httpc