Mercurial > prosody-hg
comparison net/http.lua @ 616:69bc5782b25e
Non-blocking HTTP requests (adding net.http)
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 12 Dec 2008 04:06:15 +0000 |
| parents | |
| children | b17fd66b7e10 |
comparison
equal
deleted
inserted
replaced
| 615:4ae3e81513f3 | 616:69bc5782b25e |
|---|---|
| 1 | |
| 2 local socket = require "socket" | |
| 3 local mime = require "mime" | |
| 4 local url = require "socket.url" | |
| 5 | |
| 6 local server = require "net.server" | |
| 7 | |
| 8 local connlisteners_get = require "net.connlisteners".get; | |
| 9 local listener = connlisteners_get("httpclient") or error("No httpclient listener!"); | |
| 10 | |
| 11 local t_insert, t_concat = table.insert, table.concat; | |
| 12 local tonumber, tostring, pairs = tonumber, tostring, pairs; | |
| 13 local print = function () end | |
| 14 | |
| 15 local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); | |
| 16 local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%x", c:byte()); end)); end | |
| 17 | |
| 18 module "http" | |
| 19 | |
| 20 local function expectbody(reqt, code) | |
| 21 if reqt.method == "HEAD" then return nil end | |
| 22 if code == 204 or code == 304 then return nil end | |
| 23 if code >= 100 and code < 200 then return nil end | |
| 24 return 1 | |
| 25 end | |
| 26 | |
| 27 local function request_reader(request, data, startpos) | |
| 28 if not data then | |
| 29 if request.body then | |
| 30 request.callback(t_concat(request.body)); | |
| 31 else | |
| 32 -- Error.. connection was closed prematurely | |
| 33 request.callback(nil, "connection-closed"); | |
| 34 end | |
| 35 destroy_request(request); | |
| 36 return; | |
| 37 end | |
| 38 if request.state == "body" then | |
| 39 print("Reading body...") | |
| 40 if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.responseheaders["content-length"]); end | |
| 41 if startpos then | |
| 42 data = data:sub(startpos, -1) | |
| 43 end | |
| 44 t_insert(request.body, data); | |
| 45 if request.bodylength then | |
| 46 request.havebodylength = request.havebodylength + #data; | |
| 47 if request.havebodylength >= request.bodylength then | |
| 48 -- We have the body | |
| 49 if request.callback then | |
| 50 request.callback(t_concat(request.body)); | |
| 51 end | |
| 52 end | |
| 53 print("", "Have "..request.havebodylength.." bytes out of "..request.bodylength); | |
| 54 end | |
| 55 elseif request.state == "headers" then | |
| 56 print("Reading headers...") | |
| 57 local pos = startpos; | |
| 58 local headers = request.responseheaders or {}; | |
| 59 for line in data:sub(startpos, -1):gmatch("(.-)\r\n") do | |
| 60 startpos = startpos + #line + 2; | |
| 61 local k, v = line:match("(%S+): (.+)"); | |
| 62 if k and v then | |
| 63 headers[k:lower()] = v; | |
| 64 print("Header: "..k:lower().." = "..v); | |
| 65 elseif #line == 0 then | |
| 66 request.responseheaders = headers; | |
| 67 break; | |
| 68 else | |
| 69 print("Unhandled header line: "..line); | |
| 70 end | |
| 71 end | |
| 72 -- Reached the end of the headers | |
| 73 request.state = "body"; | |
| 74 if #data > startpos then | |
| 75 return request_reader(request, data, startpos); | |
| 76 end | |
| 77 elseif request.state == "status" then | |
| 78 print("Reading status...") | |
| 79 local http, code, text, linelen = data:match("^HTTP/(%S+) (%d+) (.-)\r\n()", startpos); | |
| 80 if not code then | |
| 81 return request.callback(nil, "invalid-status-line", data); | |
| 82 end | |
| 83 | |
| 84 request.responsecode, request.responseversion = code, http; | |
| 85 | |
| 86 if request.onlystatus or not expectbody(request, tonumber(code)) then | |
| 87 if request.callback then | |
| 88 request.callback(code); | |
| 89 end | |
| 90 destroy_request(request); | |
| 91 return; | |
| 92 end | |
| 93 | |
| 94 request.state = "headers"; | |
| 95 | |
| 96 if #data > linelen then | |
| 97 return request_reader(request, data, linelen); | |
| 98 end | |
| 99 end | |
| 100 end | |
| 101 | |
| 102 function request(u, callback, ex) | |
| 103 local req = url.parse(u); | |
| 104 | |
| 105 local custom_headers, body; | |
| 106 local default_headers = { ["Host"] = req.host, ["User-Agent"] = "Prosody XMPP Server" } | |
| 107 | |
| 108 | |
| 109 if req.userinfo then | |
| 110 default_headers["Authorization"] = "Basic "..mime.b64(req.userinfo); | |
| 111 end | |
| 112 | |
| 113 if ex then | |
| 114 custom_headers = ex.custom_headers; | |
| 115 req.onlystatus = ex.onlystatus; | |
| 116 body = ex.body; | |
| 117 if body then | |
| 118 req.method = "POST "; | |
| 119 default_headers["Content-Length"] = tostring(#body); | |
| 120 default_headers["Content-Type"] = "application/x-www-form-urlencoded"; | |
| 121 end | |
| 122 if ex.method then req.method = ex.method; end | |
| 123 end | |
| 124 | |
| 125 req.handler, req.conn = server.wraptcpclient(listener, socket.tcp(), req.host, req.port or 80, 0, "*a"); | |
| 126 req.write = req.handler.write; | |
| 127 req.conn:settimeout(0); | |
| 128 local ok, err = req.conn:connect(req.host, req.port or 80); | |
| 129 if not ok and err ~= "timeout" then | |
| 130 return nil, err; | |
| 131 end | |
| 132 | |
| 133 req.write((req.method or "GET ")..req.path.." HTTP/1.0\r\n"); | |
| 134 local t = { [2] = ": ", [4] = "\r\n" }; | |
| 135 if custom_headers then | |
| 136 for k, v in pairs(custom_headers) do | |
| 137 t[1], t[3] = k, v; | |
| 138 req.write(t_concat(t)); | |
| 139 default_headers[k] = nil; | |
| 140 end | |
| 141 end | |
| 142 | |
| 143 for k, v in pairs(default_headers) do | |
| 144 t[1], t[3] = k, v; | |
| 145 req.write(t_concat(t)); | |
| 146 default_headers[k] = nil; | |
| 147 end | |
| 148 req.write("\r\n"); | |
| 149 | |
| 150 if body then | |
| 151 req.write(body); | |
| 152 end | |
| 153 | |
| 154 req.callback = callback; | |
| 155 req.reader = request_reader; | |
| 156 req.state = "status" | |
| 157 | |
| 158 listener.register_request(req.handler, req); | |
| 159 | |
| 160 server.loop() | |
| 161 return req; | |
| 162 end | |
| 163 | |
| 164 function destroy_request(request) | |
| 165 if request.conn then | |
| 166 request.handler.close() | |
| 167 listener.disconnect(request.conn, "closed"); | |
| 168 end | |
| 169 end | |
| 170 | |
| 171 _M.urlencode = urlencode; | |
| 172 | |
| 173 return _M; |
