Software /
code /
prosody
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 |
child | 617: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; |