diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-07-29 03:51:36 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-07-29 03:51:36 +0000 |
commit | 3143c58e0fe839e849aca3b29e0928bd9c1df6a5 (patch) | |
tree | 118cacf8df04e81eb1d132dc06217f6eb0b6668f /src | |
parent | 0cc37c4b41d8762a62a2939ac100ef8b77205c44 (diff) | |
download | luasocket-3143c58e0fe839e849aca3b29e0928bd9c1df6a5.tar.gz luasocket-3143c58e0fe839e849aca3b29e0928bd9c1df6a5.tar.bz2 luasocket-3143c58e0fe839e849aca3b29e0928bd9c1df6a5.zip |
Rewritten to comply to LTN7 (Modules & Packages).
As a result, there have been some API changes.
Parameter and return values are now passed inside tables.
Automatic redirection and automatic authentication are better controlled,
with loop detection.
Implementation is more RFCish, conforming to RFC2616.
URL parsing has been moved to an external library, to be shared with FTP.
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 |