diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/http.lua | 556 |
1 files changed, 311 insertions, 245 deletions
diff --git a/src/http.lua b/src/http.lua index 576118e..e68cf22 100644 --- a/src/http.lua +++ b/src/http.lua | |||
@@ -1,22 +1,32 @@ | |||
1 | ----------------------------------------------------------------------------- | 1 | ----------------------------------------------------------------------------- |
2 | -- Full HTTP/1.1 client support for the Lua language using the | 2 | -- Full HTTP/1.1 client support for the Lua language using the |
3 | -- LuaSocket 1.2 toolkit. | 3 | -- LuaSocket 1.4a toolkit. |
4 | -- Author: Diego Nehab | 4 | -- Author: Diego Nehab |
5 | -- Date: 26/12/2000 | 5 | -- Date: 26/12/2000 |
6 | -- Conforming to: RFC 2068 | 6 | -- Conforming to: RFC 2068 |
7 | ----------------------------------------------------------------------------- | 7 | ----------------------------------------------------------------------------- |
8 | 8 | ||
9 | local Public, Private = {}, {} | ||
10 | HTTP = Public | ||
11 | |||
9 | ----------------------------------------------------------------------------- | 12 | ----------------------------------------------------------------------------- |
10 | -- Program constants | 13 | -- Program constants |
11 | ----------------------------------------------------------------------------- | 14 | ----------------------------------------------------------------------------- |
12 | -- connection timeout in seconds | 15 | -- connection timeout in seconds |
13 | local TIMEOUT = 60 | 16 | Private.TIMEOUT = 60 |
14 | -- default port for document retrieval | 17 | -- default port for document retrieval |
15 | local PORT = 80 | 18 | Private.PORT = 80 |
16 | -- user agent field sent in request | 19 | -- user agent field sent in request |
17 | local USERAGENT = "LuaSocket 1.3b HTTP 1.1" | 20 | Private.USERAGENT = "LuaSocket 1.4a" |
18 | -- block size used in transfers | 21 | -- block size used in transfers |
19 | local BLOCKSIZE = 8192 | 22 | Private.BLOCKSIZE = 8192 |
23 | |||
24 | ----------------------------------------------------------------------------- | ||
25 | -- Required libraries | ||
26 | ----------------------------------------------------------------------------- | ||
27 | dofile "buffer.lua" | ||
28 | dofile "url.lua" | ||
29 | dofile "encode.lua" | ||
20 | 30 | ||
21 | ----------------------------------------------------------------------------- | 31 | ----------------------------------------------------------------------------- |
22 | -- Tries to get a pattern from the server and closes socket on error | 32 | -- Tries to get a pattern from the server and closes socket on error |
@@ -26,9 +36,9 @@ local BLOCKSIZE = 8192 | |||
26 | -- data: line received or nil in case of error | 36 | -- data: line received or nil in case of error |
27 | -- err: error message if any | 37 | -- err: error message if any |
28 | ----------------------------------------------------------------------------- | 38 | ----------------------------------------------------------------------------- |
29 | local try_get = function(...) | 39 | function Private.try_receive(...) |
30 | local sock = arg[1] | 40 | local sock = arg[1] |
31 | local data, err = call(sock.receive, arg) | 41 | local data, err = call(sock.receive, arg) |
32 | if err then | 42 | if err then |
33 | sock:close() | 43 | sock:close() |
34 | return nil, err | 44 | return nil, err |
@@ -43,8 +53,8 @@ end | |||
43 | -- Returns | 53 | -- Returns |
44 | -- err: error message if any, nil if successfull | 54 | -- err: error message if any, nil if successfull |
45 | ----------------------------------------------------------------------------- | 55 | ----------------------------------------------------------------------------- |
46 | local try_send = function(sock, data) | 56 | function Private.try_send(sock, data) |
47 | err = sock:send(data) | 57 | local err = sock:send(data) |
48 | if err then sock:close() end | 58 | if err then sock:close() end |
49 | return err | 59 | return err |
50 | end | 60 | end |
@@ -56,13 +66,14 @@ end | |||
56 | -- Returns | 66 | -- Returns |
57 | -- code: integer with status code | 67 | -- code: integer with status code |
58 | ----------------------------------------------------------------------------- | 68 | ----------------------------------------------------------------------------- |
59 | local get_statuscode = function(line) | 69 | function Private.get_statuscode(line) |
60 | local _,_, code = strfind(line, " (%d%d%d) ") | 70 | local code, _ |
71 | _, _, code = strfind(line, "HTTP/%d*%.%d* (%d%d%d)") | ||
61 | return tonumber(code) | 72 | return tonumber(code) |
62 | end | 73 | end |
63 | 74 | ||
64 | ----------------------------------------------------------------------------- | 75 | ----------------------------------------------------------------------------- |
65 | -- Receive server reply messages | 76 | -- Receive server reply messages, parsing status code |
66 | -- Input | 77 | -- Input |
67 | -- sock: socket connected to the server | 78 | -- sock: socket connected to the server |
68 | -- Returns | 79 | -- Returns |
@@ -70,29 +81,29 @@ end | |||
70 | -- line: full http status line | 81 | -- line: full http status line |
71 | -- err: error message if any | 82 | -- err: error message if any |
72 | ----------------------------------------------------------------------------- | 83 | ----------------------------------------------------------------------------- |
73 | local get_status = function(sock) | 84 | function Private.receive_status(sock) |
74 | local line, err | 85 | local line, err |
75 | line, err = %try_get(sock) | 86 | line, err = %Private.try_receive(sock) |
76 | if not err then return %get_statuscode(line), line | 87 | if not err then return %Private.get_statuscode(line), line |
77 | else return nil, nil, err end | 88 | else return nil, nil, err end |
78 | end | 89 | end |
79 | 90 | ||
80 | ----------------------------------------------------------------------------- | 91 | ----------------------------------------------------------------------------- |
81 | -- Receive and parse responce header fields | 92 | -- Receive and parse response header fields |
82 | -- Input | 93 | -- Input |
83 | -- sock: socket connected to the server | 94 | -- sock: socket connected to the server |
84 | -- hdrs: a table that might already contain headers | 95 | -- headers: a table that might already contain headers |
85 | -- Returns | 96 | -- Returns |
86 | -- hdrs: a table with all headers fields in the form | 97 | -- headers: a table with all headers fields in the form |
87 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} | 98 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} |
88 | -- all name_i are lowercase | 99 | -- all name_i are lowercase |
89 | -- nil and error message in case of error | 100 | -- nil and error message in case of error |
90 | ----------------------------------------------------------------------------- | 101 | ----------------------------------------------------------------------------- |
91 | local get_hdrs = function(sock, hdrs) | 102 | function Private.receive_headers(sock, headers) |
92 | local line, err | 103 | local line, err |
93 | local name, value | 104 | local name, value, _ |
94 | -- get first line | 105 | -- get first line |
95 | line, err = %try_get(sock) | 106 | line, err = %Private.try_receive(sock) |
96 | if err then return nil, err end | 107 | if err then return nil, err end |
97 | -- headers go until a blank line is found | 108 | -- headers go until a blank line is found |
98 | while line ~= "" do | 109 | while line ~= "" do |
@@ -104,34 +115,35 @@ local get_hdrs = function(sock, hdrs) | |||
104 | end | 115 | end |
105 | name = strlower(name) | 116 | name = strlower(name) |
106 | -- get next line (value might be folded) | 117 | -- get next line (value might be folded) |
107 | line, err = %try_get(sock) | 118 | line, err = %Private.try_receive(sock) |
108 | if err then return nil, err end | 119 | if err then return nil, err end |
109 | -- unfold any folded values | 120 | -- unfold any folded values |
110 | while not err and strfind(line, "^%s") do | 121 | while not err and strfind(line, "^%s") do |
111 | value = value .. line | 122 | value = value .. line |
112 | line, err = %try_get(sock) | 123 | line, err = %Private.try_receive(sock) |
113 | if err then return nil, err end | 124 | if err then return nil, err end |
114 | end | 125 | end |
115 | -- save pair in table | 126 | -- save pair in table |
116 | if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value | 127 | if headers[name] then headers[name] = headers[name] .. ", " .. value |
117 | else hdrs[name] = value end | 128 | else headers[name] = value end |
118 | end | 129 | end |
119 | return hdrs | 130 | return headers |
120 | end | 131 | end |
121 | 132 | ||
122 | ----------------------------------------------------------------------------- | 133 | ----------------------------------------------------------------------------- |
123 | -- Receives a chunked message body | 134 | -- Receives a chunked message body |
124 | -- Input | 135 | -- Input |
125 | -- sock: socket connected to the server | 136 | -- sock: socket connected to the server |
137 | -- headers: header set in which to include trailer headers | ||
126 | -- receive_cb: function to receive chunks | 138 | -- receive_cb: function to receive chunks |
127 | -- Returns | 139 | -- Returns |
128 | -- nil if successfull or an error message in case of error | 140 | -- nil if successfull or an error message in case of error |
129 | ----------------------------------------------------------------------------- | 141 | ----------------------------------------------------------------------------- |
130 | local try_getchunked = function(sock, receive_cb) | 142 | function Private.receivebody_bychunks(sock, headers, receive_cb) |
131 | local chunk, size, line, err, go, uerr, _ | 143 | local chunk, size, line, err, go, uerr, _ |
132 | repeat | 144 | while 1 do |
133 | -- get chunk size, skip extention | 145 | -- get chunk size, skip extention |
134 | line, err = %try_get(sock) | 146 | line, err = %Private.try_receive(sock) |
135 | if err then | 147 | if err then |
136 | local _, uerr = receive_cb(nil, err) | 148 | local _, uerr = receive_cb(nil, err) |
137 | return uerr or err | 149 | return uerr or err |
@@ -143,8 +155,10 @@ local try_getchunked = function(sock, receive_cb) | |||
143 | _, uerr = receive_cb(nil, err) | 155 | _, uerr = receive_cb(nil, err) |
144 | return uerr or err | 156 | return uerr or err |
145 | end | 157 | end |
158 | -- was it the last chunk? | ||
159 | if size <= 0 then break end | ||
146 | -- get chunk | 160 | -- get chunk |
147 | chunk, err = %try_get(sock, size) | 161 | chunk, err = %Private.try_receive(sock, size) |
148 | if err then | 162 | if err then |
149 | _, uerr = receive_cb(nil, err) | 163 | _, uerr = receive_cb(nil, err) |
150 | return uerr or err | 164 | return uerr or err |
@@ -155,13 +169,21 @@ local try_getchunked = function(sock, receive_cb) | |||
155 | sock:close() | 169 | sock:close() |
156 | return uerr or "aborted by callback" | 170 | return uerr or "aborted by callback" |
157 | end | 171 | end |
158 | -- skip blank line | 172 | -- skip CRLF on end of chunk |
159 | _, err = %try_get(sock) | 173 | _, err = %Private.try_receive(sock) |
160 | if err then | 174 | if err then |
161 | _, uerr = receive_cb(nil, err) | 175 | _, uerr = receive_cb(nil, err) |
162 | return uerr or err | 176 | return uerr or err |
163 | end | 177 | end |
164 | until size <= 0 | 178 | end |
179 | -- the server should not send trailer headers because we didn't send a | ||
180 | -- header informing it we know how to deal with them. we do not risk | ||
181 | -- being caught unprepaired. | ||
182 | headers, err = %Private.receive_headers(sock, headers) | ||
183 | if err then | ||
184 | _, uerr = receive_cb(nil, err) | ||
185 | return uerr or err | ||
186 | end | ||
165 | -- let callback know we are done | 187 | -- let callback know we are done |
166 | _, uerr = receive_cb("") | 188 | _, uerr = receive_cb("") |
167 | return uerr | 189 | return uerr |
@@ -175,10 +197,10 @@ end | |||
175 | -- Returns | 197 | -- Returns |
176 | -- nil if successfull or an error message in case of error | 198 | -- nil if successfull or an error message in case of error |
177 | ----------------------------------------------------------------------------- | 199 | ----------------------------------------------------------------------------- |
178 | local try_getbylength = function(sock, length, receive_cb) | 200 | function Private.receivebody_bylength(sock, length, receive_cb) |
179 | local uerr, go | 201 | local uerr, go |
180 | while length > 0 do | 202 | while length > 0 do |
181 | local size = min(%BLOCKSIZE, length) | 203 | local size = min(%Private.BLOCKSIZE, length) |
182 | local chunk, err = sock:receive(size) | 204 | local chunk, err = sock:receive(size) |
183 | if err then | 205 | if err then |
184 | go, uerr = receive_cb(nil, err) | 206 | go, uerr = receive_cb(nil, err) |
@@ -203,10 +225,10 @@ end | |||
203 | -- Returns | 225 | -- Returns |
204 | -- nil if successfull or an error message in case of error | 226 | -- nil if successfull or an error message in case of error |
205 | ----------------------------------------------------------------------------- | 227 | ----------------------------------------------------------------------------- |
206 | local try_getuntilclosed = function(sock, receive_cb) | 228 | function Private.receivebody_untilclosed(sock, receive_cb) |
207 | local err, go, uerr | 229 | local err, go, uerr |
208 | while 1 do | 230 | while 1 do |
209 | local chunk, err = sock:receive(%BLOCKSIZE) | 231 | local chunk, err = sock:receive(%Private.BLOCKSIZE) |
210 | if err == "closed" or not err then | 232 | if err == "closed" or not err then |
211 | go, uerr = receive_cb(chunk) | 233 | go, uerr = receive_cb(chunk) |
212 | if not go then | 234 | if not go then |
@@ -227,62 +249,36 @@ end | |||
227 | -- Receives http response body | 249 | -- Receives http response body |
228 | -- Input | 250 | -- Input |
229 | -- sock: socket connected to the server | 251 | -- sock: socket connected to the server |
230 | -- resp_hdrs: response header fields | 252 | -- headers: response header fields |
231 | -- receive_cb: function to receive chunks | 253 | -- receive_cb: function to receive chunks |
232 | -- Returns | 254 | -- Returns |
233 | -- nil if successfull or an error message in case of error | 255 | -- nil if successfull or an error message in case of error |
234 | ----------------------------------------------------------------------------- | 256 | ----------------------------------------------------------------------------- |
235 | local try_getbody = function(sock, resp_hdrs, receive_cb) | 257 | function Private.receive_body(sock, headers, receive_cb) |
236 | local err | 258 | local te = headers["transfer-encoding"] |
237 | if resp_hdrs["transfer-encoding"] == "chunked" then | 259 | if te and te ~= "identity" then |
238 | -- get by chunked transfer-coding of message body | 260 | -- get by chunked transfer-coding of message body |
239 | return %try_getchunked(sock, receive_cb) | 261 | return %Private.receivebody_bychunks(sock, headers, receive_cb) |
240 | elseif tonumber(resp_hdrs["content-length"]) then | 262 | elseif tonumber(headers["content-length"]) then |
241 | -- get by content-length | 263 | -- get by content-length |
242 | local length = tonumber(resp_hdrs["content-length"]) | 264 | local length = tonumber(headers["content-length"]) |
243 | return %try_getbylength(sock, length, receive_cb) | 265 | return %Private.receivebody_bylength(sock, length, receive_cb) |
244 | else | 266 | else |
245 | -- get it all until connection closes | 267 | -- get it all until connection closes |
246 | return %try_getuntilclosed(sock, receive_cb) | 268 | return %Private.receivebody_untilclosed(sock, receive_cb) |
247 | end | 269 | end |
248 | end | 270 | end |
249 | 271 | ||
250 | ----------------------------------------------------------------------------- | 272 | ----------------------------------------------------------------------------- |
251 | -- Parses a url and returns its scheme, user, password, host, port | 273 | -- Drop http response body |
252 | -- and path components, according to RFC 1738 | ||
253 | -- Input | 274 | -- Input |
254 | -- url: uniform resource locator of request | 275 | -- sock: socket connected to the server |
255 | -- default: table containing default values to be returned | 276 | -- headers: response header fields |
256 | -- Returns | 277 | -- Returns |
257 | -- table with the following fields: | 278 | -- nil if successfull or an error message in case of error |
258 | -- host: host to connect | 279 | ----------------------------------------------------------------------------- |
259 | -- path: url path | 280 | function Private.drop_body(sock, headers) |
260 | -- port: host port to connect | 281 | return %Private.receive_body(sock, headers, function (c, e) return 1 end) |
261 | -- user: user name | ||
262 | -- pass: password | ||
263 | -- scheme: protocol | ||
264 | ----------------------------------------------------------------------------- | ||
265 | local split_url = function(url, default) | ||
266 | -- initialize default parameters | ||
267 | local parsed = default or {} | ||
268 | -- get scheme | ||
269 | url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) | ||
270 | -- get user name and password. both can be empty! | ||
271 | -- moreover, password can be ommited | ||
272 | url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) | ||
273 | %parsed.user = u | ||
274 | -- there can be an empty password, but the ':' has to be there | ||
275 | -- or else there is no password | ||
276 | %parsed.pass = nil -- kill default password | ||
277 | if c == ":" then %parsed.pass = p end | ||
278 | end) | ||
279 | -- get host | ||
280 | url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) | ||
281 | -- get port if any | ||
282 | url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) | ||
283 | -- whatever is left is the path | ||
284 | if url ~= "" then parsed.path = url end | ||
285 | return parsed | ||
286 | end | 282 | end |
287 | 283 | ||
288 | ----------------------------------------------------------------------------- | 284 | ----------------------------------------------------------------------------- |
@@ -294,20 +290,20 @@ end | |||
294 | -- Returns | 290 | -- Returns |
295 | -- nil if successfull, or an error message in case of error | 291 | -- nil if successfull, or an error message in case of error |
296 | ----------------------------------------------------------------------------- | 292 | ----------------------------------------------------------------------------- |
297 | local try_sendindirect = function(data, send_cb, chunk, size) | 293 | function Private.send_indirect(data, send_cb, chunk, size) |
298 | local sent, err | 294 | local sent, err |
299 | sent = 0 | 295 | sent = 0 |
300 | while 1 do | 296 | while 1 do |
301 | if type(chunk) ~= "string" or type(size) ~= "number" then | 297 | if type(chunk) ~= "string" or type(size) ~= "number" then |
302 | data:close() | 298 | data:close() |
303 | if not chunk and type(size) == "string" then return size | 299 | if not chunk and type(size) == "string" then return size |
304 | else return "invalid callback return" end | 300 | else return "invalid callback return" end |
305 | end | 301 | end |
306 | err = data:send(chunk) | 302 | err = data:send(chunk) |
307 | if err then | 303 | if err then |
308 | data:close() | 304 | data:close() |
309 | return err | 305 | return err |
310 | end | 306 | end |
311 | sent = sent + strlen(chunk) | 307 | sent = sent + strlen(chunk) |
312 | if sent >= size then break end | 308 | if sent >= size then break end |
313 | chunk, size = send_cb() | 309 | chunk, size = send_cb() |
@@ -320,243 +316,313 @@ end | |||
320 | -- sock: socket connected to the server | 316 | -- sock: socket connected to the server |
321 | -- method: request method to be used | 317 | -- method: request method to be used |
322 | -- path: url path | 318 | -- path: url path |
323 | -- req_hdrs: request headers to be sent | 319 | -- headers: request headers to be sent |
324 | -- req_body_cb: callback to send request message body | 320 | -- body_cb: callback to send request message body |
325 | -- Returns | 321 | -- Returns |
326 | -- err: nil in case of success, error message otherwise | 322 | -- err: nil in case of success, error message otherwise |
327 | ----------------------------------------------------------------------------- | 323 | ----------------------------------------------------------------------------- |
328 | local send_request = function(sock, method, path, req_hdrs, req_body_cb) | 324 | function Private.send_request(sock, method, path, headers, body_cb) |
329 | local chunk, size, done, err | 325 | local chunk, size, done, err |
330 | -- send request line | 326 | -- send request line |
331 | err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") | 327 | err = %Private.try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") |
332 | if err then return err end | 328 | if err then return err end |
333 | -- if there is a request message body, add content-length header | 329 | -- if there is a request message body, add content-length header |
334 | if req_body_cb then | 330 | if body_cb then |
335 | chunk, size = req_body_cb() | 331 | chunk, size = body_cb() |
336 | if type(chunk) == "string" and type(size) == "number" then | 332 | if type(chunk) == "string" and type(size) == "number" then |
337 | req_hdrs["content-length"] = tostring(size) | 333 | headers["content-length"] = tostring(size) |
338 | else | 334 | else |
339 | sock:close() | 335 | sock:close() |
340 | if not chunk and type(size) == "string" then return size | 336 | if not chunk and type(size) == "string" then return size |
341 | else return "invalid callback return" end | 337 | else return "invalid callback return" end |
342 | end | 338 | end |
343 | end | 339 | end |
344 | -- send request headers | 340 | -- send request headers |
345 | for i, v in req_hdrs do | 341 | for i, v in headers do |
346 | err = %try_send(sock, i .. ": " .. v .. "\r\n") | 342 | err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") |
347 | if err then return err end | 343 | if err then return err end |
348 | end | 344 | end |
349 | -- mark end of request headers | 345 | -- mark end of request headers |
350 | err = %try_send(sock, "\r\n") | 346 | err = %Private.try_send(sock, "\r\n") |
351 | if err then return err end | 347 | if err then return err end |
352 | -- send request message body, if any | 348 | -- send request message body, if any |
353 | if req_body_cb then | 349 | if body_cb then |
354 | return %try_sendindirect(sock, req_body_cb, chunk, size) | 350 | return %Private.send_indirect(sock, body_cb, chunk, size) |
355 | end | 351 | end |
356 | end | 352 | end |
357 | 353 | ||
358 | ----------------------------------------------------------------------------- | 354 | ----------------------------------------------------------------------------- |
359 | -- Determines if we should read a message body from the server response | 355 | -- Determines if we should read a message body from the server response |
360 | -- Input | 356 | -- Input |
361 | -- method: method used in request | 357 | -- request: a table with the original request information |
362 | -- code: server response status code | 358 | -- response: a table with the server response information |
363 | -- Returns | 359 | -- Returns |
364 | -- 1 if a message body should be processed, nil otherwise | 360 | -- 1 if a message body should be processed, nil otherwise |
365 | ----------------------------------------------------------------------------- | 361 | ----------------------------------------------------------------------------- |
366 | function has_respbody(method, code) | 362 | function Private.has_body(request, response) |
367 | if method == "HEAD" then return nil end | 363 | if request.method == "HEAD" then return nil end |
368 | if code == 204 or code == 304 then return nil end | 364 | if response.code == 204 or response.code == 304 then return nil end |
369 | if code >= 100 and code < 200 then return nil end | 365 | if response.code >= 100 and response.code < 200 then return nil end |
370 | return 1 | 366 | return 1 |
371 | end | 367 | end |
372 | 368 | ||
373 | ----------------------------------------------------------------------------- | 369 | ----------------------------------------------------------------------------- |
374 | -- We need base64 convertion routines for Basic Authentication Scheme | ||
375 | ----------------------------------------------------------------------------- | ||
376 | dofile("base64.lua") | ||
377 | |||
378 | ----------------------------------------------------------------------------- | ||
379 | -- Converts field names to lowercase and adds a few needed headers | 370 | -- Converts field names to lowercase and adds a few needed headers |
380 | -- Input | 371 | -- Input |
381 | -- hdrs: request header fields | 372 | -- headers: request header fields |
382 | -- parsed: parsed url components | 373 | -- parsed: parsed url components |
383 | -- Returns | 374 | -- Returns |
384 | -- lower: a table with the same headers, but with lowercase field names | 375 | -- lower: a table with the same headers, but with lowercase field names |
385 | ----------------------------------------------------------------------------- | 376 | ----------------------------------------------------------------------------- |
386 | local fill_hdrs = function(hdrs, parsed) | 377 | function Private.fill_headers(headers, parsed) |
387 | local lower = {} | 378 | local lower = {} |
388 | hdrs = hdrs or {} | 379 | headers = headers or {} |
389 | for i,v in hdrs do | 380 | -- set default headers |
381 | lower["user-agent"] = %Private.USERAGENT | ||
382 | lower["host"] = parsed.host | ||
383 | -- override with user values | ||
384 | for i,v in headers do | ||
390 | lower[strlower(i)] = v | 385 | lower[strlower(i)] = v |
391 | end | 386 | end |
387 | -- this cannot be overriden | ||
392 | lower["connection"] = "close" | 388 | lower["connection"] = "close" |
393 | lower["host"] = parsed.host | ||
394 | lower["user-agent"] = %USERAGENT | ||
395 | if parsed.user and parsed.pass then -- Basic Authentication | ||
396 | lower["authorization"] = "Basic ".. | ||
397 | base64(parsed.user .. ":" .. parsed.pass) | ||
398 | end | ||
399 | return lower | 389 | return lower |
400 | end | 390 | end |
401 | 391 | ||
402 | ----------------------------------------------------------------------------- | 392 | ----------------------------------------------------------------------------- |
403 | -- Sends a HTTP request and retrieves the server reply using callbacks to | 393 | -- Decides wether we should follow retry with authorization formation |
404 | -- send the request body and receive the response body | ||
405 | -- Input | 394 | -- Input |
406 | -- method: "GET", "PUT", "POST" etc | 395 | -- request: a table with the original request information |
407 | -- url: target uniform resource locator | 396 | -- parsed: parsed request url |
408 | -- resp_body_cb: response message body receive callback | 397 | -- response: a table with the server response information |
409 | -- req_hdrs: request headers to send, or nil if none | ||
410 | -- req_body_cb: request message body send callback, or nil if none | ||
411 | -- stay: should we refrain from following a server redirect message? | ||
412 | -- Returns | 398 | -- Returns |
413 | -- resp_hdrs: response header fields received, or nil if failed | 399 | -- 1 if we should retry, nil otherwise |
414 | -- resp_line: server response status line, or nil if failed | 400 | ----------------------------------------------------------------------------- |
415 | -- err: error message, or nil if successfull | 401 | function Private.should_authorize(request, parsed, response) |
416 | ----------------------------------------------------------------------------- | 402 | -- if there has been an authorization attempt, it must have failed |
417 | function http_requestindirect(method, url, resp_body_cb, req_hdrs, | 403 | if request.headers["authorization"] then return nil end |
418 | req_body_cb, stay) | 404 | -- if we don't have authorization information, we can't retry |
419 | local sock, err | 405 | if parsed.user and parsed.password then return 1 |
420 | local resp_hdrs | 406 | else return nil end |
421 | local resp_line, resp_code | ||
422 | -- get url components | ||
423 | local parsed = %split_url(url, {port = %PORT, path ="/"}) | ||
424 | -- methods are case sensitive | ||
425 | method = strupper(method) | ||
426 | -- fill default headers | ||
427 | req_hdrs = %fill_hdrs(req_hdrs, parsed) | ||
428 | -- try connection | ||
429 | sock, err = connect(parsed.host, parsed.port) | ||
430 | if not sock then return nil, nil, err end | ||
431 | -- set connection timeout | ||
432 | sock:timeout(%TIMEOUT) | ||
433 | -- send request | ||
434 | err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) | ||
435 | if err then return nil, nil, err end | ||
436 | -- get server message | ||
437 | resp_code, resp_line, err = %get_status(sock) | ||
438 | if err then return nil, nil, err end | ||
439 | -- deal with reply | ||
440 | resp_hdrs, err = %get_hdrs(sock, {}) | ||
441 | if err then return nil, line, err end | ||
442 | -- did we get a redirect? should we automatically retry? | ||
443 | if not stay and (resp_code == 301 or resp_code == 302) and | ||
444 | (method == "GET" or method == "HEAD") then | ||
445 | sock:close() | ||
446 | return http_requestindirect(method, resp_hdrs["location"], | ||
447 | resp_body_cb, req_hdrs, req_body_cb, stay) | ||
448 | end | ||
449 | -- get response message body if status and method combination allow one | ||
450 | if has_respbody(method, resp_code) then | ||
451 | err = %try_getbody(sock, resp_hdrs, resp_body_cb) | ||
452 | if err then return resp_hdrs, resp_line, err end | ||
453 | end | ||
454 | sock:close() | ||
455 | return resp_hdrs, resp_line | ||
456 | end | 407 | end |
457 | 408 | ||
458 | ----------------------------------------------------------------------------- | 409 | ----------------------------------------------------------------------------- |
459 | -- We need fast concatenation routines for direct requests | 410 | -- Returns the result of retrying a request with authorization information |
460 | ----------------------------------------------------------------------------- | 411 | -- Input |
461 | dofile("buffer.lua") | 412 | -- request: a table with the original request information |
413 | -- parsed: parsed request url | ||
414 | -- response: a table with the server response information | ||
415 | -- Returns | ||
416 | -- response: result of target redirection | ||
417 | ----------------------------------------------------------------------------- | ||
418 | function Private.authorize(request, parsed, response) | ||
419 | request.headers["authorization"] = "Basic " .. | ||
420 | base64(parsed.user .. ":" .. parsed.password) | ||
421 | local authorize = { | ||
422 | redirects = request.redirects, | ||
423 | method = request.method, | ||
424 | url = request.url, | ||
425 | body_cb = request.body_cb, | ||
426 | headers = request.headers | ||
427 | } | ||
428 | return %Public.request_indirect(authorize, response) | ||
429 | end | ||
462 | 430 | ||
463 | ----------------------------------------------------------------------------- | 431 | ----------------------------------------------------------------------------- |
464 | -- Sends a HTTP request and retrieves the server reply | 432 | -- Decides wether we should follow a server redirect message |
465 | -- Input | 433 | -- Input |
466 | -- method: "GET", "PUT", "POST" etc | 434 | -- request: a table with the original request information |
467 | -- url: target uniform resource locator | 435 | -- response: a table with the server response information |
468 | -- req_hdrs: request headers to send, or nil if none | ||
469 | -- req_body: request message body as a string, or nil if none | ||
470 | -- stay: should we refrain from following a server redirect message? | ||
471 | -- Returns | 436 | -- Returns |
472 | -- resp_body: response message body, or nil if failed | 437 | -- 1 if we should redirect, nil otherwise |
473 | -- resp_hdrs: response header fields received, or nil if failed | 438 | ----------------------------------------------------------------------------- |
474 | -- resp_line: server response status line, or nil if failed | 439 | function Private.should_redirect(request, response) |
475 | -- err: error message, or nil if successfull | 440 | local follow = not request.stay |
476 | ----------------------------------------------------------------------------- | 441 | follow = follow and (response.code == 301 or response.code == 302) |
477 | function http_request(method, url, req_hdrs, req_body, stay) | 442 | follow = follow and (request.method == "GET" or request.method == "HEAD") |
478 | local resp_hdrs, resp_line, err | 443 | follow = follow and not (request.redirects and request.redirects >= 5) |
479 | local req_body_cb = function() | 444 | return follow |
480 | return %req_body, strlen(%req_body) | ||
481 | end | ||
482 | local resp_body = { buf = buf_create() } | ||
483 | local resp_body_cb = function(chunk, err) | ||
484 | if not chunk then %resp_body.buf = nil end | ||
485 | buf_addstring(%resp_body.buf, chunk) | ||
486 | return 1 | ||
487 | end | ||
488 | if not req_body then req_body_cb = nil end | ||
489 | resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, | ||
490 | req_hdrs, req_body_cb, stay) | ||
491 | return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err | ||
492 | end | 445 | end |
493 | 446 | ||
494 | ----------------------------------------------------------------------------- | 447 | ----------------------------------------------------------------------------- |
495 | -- Retrieves a URL by the method "GET" | 448 | -- Returns the result of a request following a server redirect message. |
496 | -- Input | 449 | -- Input |
497 | -- url: target uniform resource locator | 450 | -- request: a table with the original request information |
498 | -- req_hdrs: request headers to send, or nil if none | 451 | -- response: a table with the following fields: |
499 | -- stay: should we refrain from following a server redirect message? | 452 | -- body_cb: response method body receive-callback |
500 | -- Returns | 453 | -- Returns |
501 | -- resp_body: response message body, or nil if failed | 454 | -- response: result of target redirection |
502 | -- resp_hdrs: response header fields received, or nil if failed | 455 | ----------------------------------------------------------------------------- |
503 | -- resp_line: server response status line, or nil if failed | 456 | function Private.redirect(request, response) |
504 | -- err: error message, or nil if successfull | 457 | local redirects = request.redirects or 0 |
458 | redirects = redirects + 1 | ||
459 | local redirect = { | ||
460 | redirects = redirects, | ||
461 | method = request.method, | ||
462 | -- the RFC says the redirect url has to be absolute, but some | ||
463 | -- servers do not respect that | ||
464 | url = URL.build_absolute(request.url,response.headers["location"]), | ||
465 | body_cb = request.body_cb, | ||
466 | headers = request.headers | ||
467 | } | ||
468 | return %Public.request_indirect(redirect, response) | ||
469 | end | ||
470 | |||
505 | ----------------------------------------------------------------------------- | 471 | ----------------------------------------------------------------------------- |
506 | function http_get(url, req_hdrs, stay) | 472 | -- Computes the request URI from the given URL |
507 | return http_request("GET", url, req_hdrs, stay) | 473 | -- Input |
474 | -- parsed: parsed url | ||
475 | -- Returns | ||
476 | -- uri: request URI for parsed URL | ||
477 | ----------------------------------------------------------------------------- | ||
478 | function Private.request_uri(parsed) | ||
479 | local uri = "" | ||
480 | if parsed.path then uri = uri .. parsed.path end | ||
481 | if parsed.params then uri = uri .. ";" .. parsed.params end | ||
482 | if parsed.query then uri = uri .. "?" .. parsed.query end | ||
483 | if parsed.fragment then uri = uri .. "#" .. parsed.fragment end | ||
484 | return uri | ||
508 | end | 485 | end |
509 | 486 | ||
510 | ----------------------------------------------------------------------------- | 487 | ----------------------------------------------------------------------------- |
511 | -- Retrieves a URL by the method "GET" | 488 | -- Sends a HTTP request and retrieves the server reply using callbacks to |
489 | -- send the request body and receive the response body | ||
512 | -- Input | 490 | -- Input |
513 | -- url: target uniform resource locator | 491 | -- request: a table with the following fields |
514 | -- resp_body_cb: response message body receive callback | 492 | -- method: "GET", "PUT", "POST" etc (defaults to "GET") |
515 | -- req_hdrs: request headers to send, or nil if none | 493 | -- url: target uniform resource locator |
516 | -- stay: should we refrain from following a server redirect message? | 494 | -- user, password: authentication information |
495 | -- headers: request headers to send, or nil if none | ||
496 | -- body_cb: request message body send-callback, or nil if none | ||
497 | -- stay: should we refrain from following a server redirect message? | ||
498 | -- response: a table with the following fields: | ||
499 | -- body_cb: response method body receive-callback | ||
517 | -- Returns | 500 | -- Returns |
518 | -- resp_body: response message body, or nil if failed | 501 | -- response: a table with the following fields: |
519 | -- resp_hdrs: response header fields received, or nil if failed | 502 | -- headers: response header fields received, or nil if failed |
520 | -- resp_line: server response status line, or nil if failed | 503 | -- status: server response status line, or nil if failed |
521 | -- err: error message, or nil if successfull | 504 | -- code: server status code, or nil if failed |
505 | -- error: error message, or nil if successfull | ||
522 | ----------------------------------------------------------------------------- | 506 | ----------------------------------------------------------------------------- |
523 | function http_getindirect(url, resp_body_cb, req_hdrs, stay) | 507 | function Public.request_indirect(request, response) |
524 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay) | 508 | -- get url components |
509 | local parsed = URL.parse(request.url, {port = %Private.PORT, path ="/"}) | ||
510 | -- explicit authentication info overrides that given by the url | ||
511 | parsed.user = request.user or parsed.user | ||
512 | parsed.password = request.password or parsed.password | ||
513 | -- default method | ||
514 | request.method = request.method or "GET" | ||
515 | -- fill default headers | ||
516 | request.headers = %Private.fill_headers(request.headers, parsed) | ||
517 | -- try to connect to server | ||
518 | local sock | ||
519 | sock, response.error = connect(parsed.host, parsed.port) | ||
520 | if not sock then return response end | ||
521 | -- set connection timeout so that we do not hang forever | ||
522 | sock:timeout(%Private.TIMEOUT) | ||
523 | -- send request message | ||
524 | response.error = %Private.send_request(sock, request.method, | ||
525 | %Private.request_uri(parsed), request.headers, request.body_cb) | ||
526 | if response.error then return response end | ||
527 | -- get server response message | ||
528 | response.code, response.status, response.error = | ||
529 | %Private.receive_status(sock) | ||
530 | if response.error then return response end | ||
531 | -- receive all headers | ||
532 | response.headers, response.error = %Private.receive_headers(sock, {}) | ||
533 | if response.error then return response end | ||
534 | -- decide what to do based on request and response parameters | ||
535 | if %Private.should_redirect(request, response) then | ||
536 | %Private.drop_body(sock, response.headers) | ||
537 | sock:close() | ||
538 | return %Private.redirect(request, response) | ||
539 | elseif %Private.should_authorize(request, parsed, response) then | ||
540 | %Private.drop_body(sock, response.headers) | ||
541 | sock:close() | ||
542 | return %Private.authorize(request, parsed, response) | ||
543 | elseif %Private.has_body(request, response) then | ||
544 | response.error = %Private.receive_body(sock, response.headers, | ||
545 | response.body_cb) | ||
546 | if response.error then return response end | ||
547 | sock:close() | ||
548 | return response | ||
549 | end | ||
550 | sock:close() | ||
525 | end | 551 | end |
526 | 552 | ||
527 | ----------------------------------------------------------------------------- | 553 | ----------------------------------------------------------------------------- |
528 | -- Retrieves a URL by the method "POST" | 554 | -- Sends a HTTP request and retrieves the server reply |
529 | -- Input | 555 | -- Input |
530 | -- method: "GET", "PUT", "POST" etc | 556 | -- request: a table with the following fields |
531 | -- url: target uniform resource locator | 557 | -- method: "GET", "PUT", "POST" etc (defaults to "GET") |
532 | -- req_hdrs: request headers to send, or nil if none | 558 | -- url: target url, i.e. the document to be retrieved |
533 | -- req_body: request message body, or nil if none | 559 | -- user, password: authentication information |
534 | -- stay: should we refrain from following a server redirect message? | 560 | -- headers: request header fields, or nil if none |
561 | -- body: request message body as a string, or nil if none | ||
562 | -- stay: should we refrain from following a server redirect message? | ||
535 | -- Returns | 563 | -- Returns |
536 | -- resp_body: response message body, or nil if failed | 564 | -- response: a table with the following fields: |
537 | -- resp_hdrs: response header fields received, or nil if failed | 565 | -- body: response message body, or nil if failed |
538 | -- resp_line: server response status line, or nil if failed | 566 | -- headers: response header fields, or nil if failed |
539 | -- err: error message, or nil if successfull | 567 | -- status: server response status line, or nil if failed |
568 | -- code: server response status code, or nil if failed | ||
569 | -- error: error message if any | ||
570 | ----------------------------------------------------------------------------- | ||
571 | function Public.request(request) | ||
572 | local response = {} | ||
573 | if request.body then | ||
574 | request.body_cb = function() | ||
575 | return %request.body, strlen(%request.body) | ||
576 | end | ||
577 | end | ||
578 | local auxiliar = { buf = buf_create() } | ||
579 | response.body_cb = function(chunk, err) | ||
580 | if not chunk then %auxiliar.buf = nil end | ||
581 | buf_addstring(%auxiliar.buf, chunk) | ||
582 | return 1 | ||
583 | end | ||
584 | %Public.request_indirect(request, response) | ||
585 | response.body = buf_getresult(auxiliar.buf) | ||
586 | response.body_cb = nil | ||
587 | return response | ||
588 | end | ||
589 | |||
540 | ----------------------------------------------------------------------------- | 590 | ----------------------------------------------------------------------------- |
541 | function http_post(url, req_body, req_hdrs, stay) | 591 | -- Retrieves a URL by the method "GET" |
542 | return http_request("POST", url, req_hdrs, req_body, stay) | 592 | -- Input |
593 | -- url: target url, i.e. the document to be retrieved | ||
594 | -- Returns | ||
595 | -- body: response message body, or nil if failed | ||
596 | -- headers: response header fields received, or nil if failed | ||
597 | -- status: server response status line, or nil if failed | ||
598 | -- error: error message if any | ||
599 | ----------------------------------------------------------------------------- | ||
600 | function Public.get(url) | ||
601 | local response = %Public.request { | ||
602 | method = "GET", | ||
603 | url = url | ||
604 | } | ||
605 | return response.body, response.headers, | ||
606 | response.status, response.error | ||
543 | end | 607 | end |
544 | 608 | ||
545 | ----------------------------------------------------------------------------- | 609 | ----------------------------------------------------------------------------- |
546 | -- Retrieves a URL by the method "POST" | 610 | -- Retrieves a URL by the method "POST" |
547 | -- Input | 611 | -- Input |
548 | -- url: target uniform resource locator | 612 | -- url: target url, i.e. the document to be retrieved |
549 | -- resp_body_cb: response message body receive callback | 613 | -- body: request message body, or nil if none |
550 | -- req_body_cb: request message body send callback | ||
551 | -- req_hdrs: request headers to send, or nil if none | ||
552 | -- stay: should we refrain from following a server redirect message? | ||
553 | -- Returns | 614 | -- Returns |
554 | -- resp_body: response message body, or nil if failed | 615 | -- body: response message body, or nil if failed |
555 | -- resp_hdrs: response header fields received, or nil if failed | 616 | -- headers: response header fields received, or nil if failed |
556 | -- resp_line: server response status line, or nil if failed | 617 | -- status: server response status line, or nil if failed |
557 | -- err: error message, or nil if successfull | 618 | -- error: error message, or nil if successfull |
558 | ----------------------------------------------------------------------------- | 619 | ----------------------------------------------------------------------------- |
559 | function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) | 620 | function Public.post(url, body) |
560 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, | 621 | local response = %Public.request { |
561 | req_body_cb, stay) | 622 | method = "POST", |
623 | url = url, | ||
624 | body = body | ||
625 | } | ||
626 | return response.body, response.headers, | ||
627 | response.status, response.error | ||
562 | end | 628 | end |