PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/lua/socket/http.lua

http://luaxyssl.googlecode.com/
Lua | 351 lines | 256 code | 27 blank | 68 comment | 46 complexity | 32145ac193c1d1d81f08b2002fa8d52f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. -----------------------------------------------------------------------------
  2. -- HTTP/1.1 client support for the Lua language.
  3. -- LuaSocket toolkit.
  4. -- Author: Diego Nehab
  5. -- RCS ID: $Id: http.lua,v 1.71 2007/10/13 23:55:20 diego Exp $
  6. -----------------------------------------------------------------------------
  7. -----------------------------------------------------------------------------
  8. -- Declare module and import dependencies
  9. -------------------------------------------------------------------------------
  10. local socket = require("socket")
  11. local url = require("socket.url")
  12. local ltn12 = require("ltn12")
  13. local mime = require("mime")
  14. local string = require("string")
  15. local base = _G
  16. local table = require("table")
  17. module("socket.http")
  18. -----------------------------------------------------------------------------
  19. -- Program constants
  20. -----------------------------------------------------------------------------
  21. -- connection timeout in seconds
  22. TIMEOUT = 60
  23. -- default port for document retrieval
  24. PORT = 80
  25. -- user agent field sent in request
  26. USERAGENT = socket._VERSION
  27. -----------------------------------------------------------------------------
  28. -- Reads MIME headers from a connection, unfolding where needed
  29. -----------------------------------------------------------------------------
  30. local function receiveheaders(sock, headers)
  31. local line, name, value, err
  32. headers = headers or {}
  33. -- get first line
  34. line, err = sock:receive()
  35. if err then return nil, err end
  36. -- headers go until a blank line is found
  37. while line ~= "" do
  38. -- get field-name and value
  39. name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
  40. if not (name and value) then return nil, "malformed reponse headers" end
  41. name = string.lower(name)
  42. -- get next line (value might be folded)
  43. line, err = sock:receive()
  44. if err then return nil, err end
  45. -- unfold any folded values
  46. while string.find(line, "^%s") do
  47. value = value .. line
  48. line = sock:receive()
  49. if err then return nil, err end
  50. end
  51. -- save pair in table
  52. if headers[name] then headers[name] = headers[name] .. ", " .. value
  53. else headers[name] = value end
  54. end
  55. return headers
  56. end
  57. -----------------------------------------------------------------------------
  58. -- Extra sources and sinks
  59. -----------------------------------------------------------------------------
  60. socket.sourcet["http-chunked"] = function(sock, headers)
  61. return base.setmetatable({
  62. getfd = function() return sock:getfd() end,
  63. dirty = function() return sock:dirty() end
  64. }, {
  65. __call = function()
  66. -- get chunk size, skip extention
  67. local line, err = sock:receive()
  68. if err then return nil, err end
  69. local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
  70. if not size then return nil, "invalid chunk size" end
  71. -- was it the last chunk?
  72. if size > 0 then
  73. local chunk, err, part
  74. local t ={}
  75. -- if not, get chunk and skip terminating CRLF
  76. chunk, err, part = sock:receive(size)
  77. if chunk then sock:receive() end
  78. return chunk or part, err
  79. else
  80. -- if it was, read trailers into headers table
  81. headers, err = receiveheaders(sock, headers)
  82. if not headers then return nil, err end
  83. end
  84. end
  85. })
  86. end
  87. socket.sinkt["http-chunked"] = function(sock)
  88. return base.setmetatable({
  89. getfd = function() return sock:getfd() end,
  90. dirty = function() return sock:dirty() end
  91. }, {
  92. __call = function(self, chunk, err)
  93. if not chunk then return sock:send("0\r\n\r\n") end
  94. local size = string.format("%X\r\n", string.len(chunk))
  95. return sock:send(size .. chunk .. "\r\n")
  96. end
  97. })
  98. end
  99. -----------------------------------------------------------------------------
  100. -- Low level HTTP API
  101. -----------------------------------------------------------------------------
  102. local metat = { __index = {} }
  103. function open(host, port, create,scheme)
  104. -- create socket with user connect function, or with default
  105. local c = socket.try((create or socket.tcp)(scheme))
  106. local h = base.setmetatable({ c = c }, metat)
  107. -- create finalized try
  108. h.try = socket.newtry(function() h:close() end)
  109. -- set timeout before connecting
  110. h.try(c:settimeout(TIMEOUT))
  111. h.try(c:connect(host, port or PORT))
  112. -- here everything worked
  113. return h
  114. end
  115. function metat.__index:sendrequestline(method, uri)
  116. local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
  117. return self.try(self.c:send(reqline))
  118. end
  119. function metat.__index:sendheaders(headers)
  120. local h = "\r\n"
  121. for i, v in base.pairs(headers) do
  122. h = i .. ": " .. v .. "\r\n" .. h
  123. end
  124. self.try(self.c:send(h))
  125. return 1
  126. end
  127. function metat.__index:sendbody(headers, source, step)
  128. source = source or ltn12.source.empty()
  129. step = step or ltn12.pump.step
  130. -- if we don't know the size in advance, send chunked and hope for the best
  131. local mode = "http-chunked"
  132. if headers["content-length"] then mode = "keep-open" end
  133. return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
  134. end
  135. function metat.__index:receivestatusline()
  136. local status = self.try(self.c:receive(5))
  137. -- identify HTTP/0.9 responses, which do not contain a status line
  138. -- this is just a heuristic, but is what the RFC recommends
  139. if status ~= "HTTP/" then return nil, status end
  140. -- otherwise proceed reading a status line
  141. status = self.try(self.c:receive("*l", status))
  142. local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
  143. return self.try(base.tonumber(code), status)
  144. end
  145. function metat.__index:receiveheaders()
  146. return self.try(receiveheaders(self.c))
  147. end
  148. function metat.__index:receivebody(headers, sink, step)
  149. sink = sink or ltn12.sink.null()
  150. step = step or ltn12.pump.step
  151. local length = base.tonumber(headers["content-length"])
  152. local t = headers["transfer-encoding"] -- shortcut
  153. local mode = "default" -- connection close
  154. if t and t ~= "identity" then mode = "http-chunked"
  155. elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
  156. return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
  157. sink, step))
  158. end
  159. function metat.__index:receive09body(status, sink, step)
  160. local source = ltn12.source.rewind(socket.source("until-closed", self.c))
  161. source(status)
  162. return self.try(ltn12.pump.all(source, sink, step))
  163. end
  164. function metat.__index:close()
  165. return self.c:close()
  166. end
  167. -----------------------------------------------------------------------------
  168. -- High level HTTP API
  169. -----------------------------------------------------------------------------
  170. local function adjusturi(reqt)
  171. local u = reqt
  172. -- if there is a proxy, we need the full url. otherwise, just a part.
  173. if not reqt.proxy and not PROXY then
  174. u = {
  175. path = socket.try(reqt.path, "invalid path 'nil'"),
  176. params = reqt.params,
  177. query = reqt.query,
  178. fragment = reqt.fragment
  179. }
  180. end
  181. return url.build(u)
  182. end
  183. local function adjustproxy(reqt)
  184. local proxy = reqt.proxy or PROXY
  185. if proxy then
  186. proxy = url.parse(proxy)
  187. return proxy.host, proxy.port or 3128
  188. else
  189. return reqt.host, reqt.port
  190. end
  191. end
  192. local function adjustheaders(reqt)
  193. -- default headers
  194. local lower = {
  195. ["user-agent"] = USERAGENT,
  196. ["host"] = reqt.host,
  197. ["connection"] = "close, TE",
  198. ["te"] = "trailers"
  199. }
  200. -- if we have authentication information, pass it along
  201. if reqt.user and reqt.password then
  202. lower["authorization"] =
  203. "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password))
  204. end
  205. -- override with user headers
  206. for i,v in base.pairs(reqt.headers or lower) do
  207. lower[string.lower(i)] = v
  208. end
  209. return lower
  210. end
  211. -- default url parts
  212. local default = {
  213. host = "",
  214. path ="/",
  215. scheme = "http"
  216. }
  217. local function adjustrequest(reqt)
  218. -- parse url if provided
  219. local nreqt = reqt.url and url.parse(reqt.url, default) or {}
  220. -- explicit components override url
  221. for i,v in base.pairs(reqt) do nreqt[i] = v end
  222. if nreqt.port == "" or not nreqt.port then nreqt.port = nreqt.scheme == "https" and 443 or 80 end
  223. socket.try(nreqt.host and nreqt.host ~= "",
  224. "invalid host '" .. base.tostring(nreqt.host) .. "'")
  225. -- compute uri if user hasn't overriden
  226. nreqt.uri = reqt.uri or adjusturi(nreqt)
  227. -- ajust host and port if there is a proxy
  228. nreqt.host, nreqt.port = adjustproxy(nreqt)
  229. -- adjust headers in request
  230. nreqt.headers = adjustheaders(nreqt)
  231. return nreqt
  232. end
  233. local function shouldredirect(reqt, code, headers)
  234. return headers.location and
  235. string.gsub(headers.location, "%s", "") ~= "" and
  236. (reqt.redirect ~= false) and
  237. (code == 301 or code == 302) and
  238. (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
  239. and (not reqt.nredirects or reqt.nredirects < 5)
  240. end
  241. local function shouldreceivebody(reqt, code)
  242. if reqt.method == "HEAD" then return nil end
  243. if code == 204 or code == 304 then return nil end
  244. if code >= 100 and code < 200 then return nil end
  245. return 1
  246. end
  247. -- forward declarations
  248. local trequest, tredirect
  249. function tredirect(reqt, location)
  250. local result, code, headers, status = trequest {
  251. -- the RFC says the redirect URL has to be absolute, but some
  252. -- servers do not respect that
  253. url = url.absolute(reqt.url, location),
  254. source = reqt.source,
  255. sink = reqt.sink,
  256. headers = reqt.headers,
  257. proxy = reqt.proxy,
  258. nredirects = (reqt.nredirects or 0) + 1,
  259. create = reqt.create
  260. }
  261. -- pass location header back as a hint we redirected
  262. headers = headers or {}
  263. headers.location = headers.location or location
  264. return result, code, headers, status
  265. end
  266. function trequest(reqt)
  267. -- we loop until we get what we want, or
  268. -- until we are sure there is no way to get it
  269. local nreqt = adjustrequest(reqt)
  270. local h = open(nreqt.host, nreqt.port, nreqt.create, nreqt.scheme)
  271. -- send request line and headers
  272. h:sendrequestline(nreqt.method, nreqt.uri)
  273. h:sendheaders(nreqt.headers)
  274. -- if there is a body, send it
  275. if nreqt.source then
  276. h:sendbody(nreqt.headers, nreqt.source, nreqt.step)
  277. end
  278. local code, status = h:receivestatusline()
  279. -- if it is an HTTP/0.9 server, simply get the body and we are done
  280. if not code then
  281. h:receive09body(status, nreqt.sink, nreqt.step)
  282. return 1, 200
  283. end
  284. local headers
  285. -- ignore any 100-continue messages
  286. while code == 100 do
  287. headers = h:receiveheaders()
  288. code, status = h:receivestatusline()
  289. end
  290. headers = h:receiveheaders()
  291. -- at this point we should have a honest reply from the server
  292. -- we can't redirect if we already used the source, so we report the error
  293. if shouldredirect(nreqt, code, headers) and not nreqt.source then
  294. h:close()
  295. return tredirect(reqt, headers.location)
  296. end
  297. -- here we are finally done
  298. if shouldreceivebody(nreqt, code) then
  299. h:receivebody(headers, nreqt.sink, nreqt.step)
  300. end
  301. h:close()
  302. return 1, code, headers, status
  303. end
  304. local function srequest(u, b)
  305. local t = {}
  306. local reqt = {
  307. url = u,
  308. sink = ltn12.sink.table(t)
  309. }
  310. if b then
  311. reqt.source = ltn12.source.string(b)
  312. reqt.headers = {
  313. ["content-length"] = string.len(b),
  314. ["content-type"] = "application/x-www-form-urlencoded"
  315. }
  316. reqt.method = "POST"
  317. end
  318. local code, headers, status = socket.skip(1, trequest(reqt))
  319. return table.concat(t), code, headers, status
  320. end
  321. request = socket.protect(function(reqt, body)
  322. if base.type(reqt) == "string" then return srequest(reqt, body)
  323. else return trequest(reqt) end
  324. end)