aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/http.lua556
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
9local Public, Private = {}, {}
10HTTP = Public
11
9----------------------------------------------------------------------------- 12-----------------------------------------------------------------------------
10-- Program constants 13-- Program constants
11----------------------------------------------------------------------------- 14-----------------------------------------------------------------------------
12-- connection timeout in seconds 15-- connection timeout in seconds
13local TIMEOUT = 60 16Private.TIMEOUT = 60
14-- default port for document retrieval 17-- default port for document retrieval
15local PORT = 80 18Private.PORT = 80
16-- user agent field sent in request 19-- user agent field sent in request
17local USERAGENT = "LuaSocket 1.3b HTTP 1.1" 20Private.USERAGENT = "LuaSocket 1.4a"
18-- block size used in transfers 21-- block size used in transfers
19local BLOCKSIZE = 8192 22Private.BLOCKSIZE = 8192
23
24-----------------------------------------------------------------------------
25-- Required libraries
26-----------------------------------------------------------------------------
27dofile "buffer.lua"
28dofile "url.lua"
29dofile "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-----------------------------------------------------------------------------
29local try_get = function(...) 39function 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-----------------------------------------------------------------------------
46local try_send = function(sock, data) 56function 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
50end 60end
@@ -56,13 +66,14 @@ end
56-- Returns 66-- Returns
57-- code: integer with status code 67-- code: integer with status code
58----------------------------------------------------------------------------- 68-----------------------------------------------------------------------------
59local get_statuscode = function(line) 69function 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)
62end 73end
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-----------------------------------------------------------------------------
73local get_status = function(sock) 84function 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
78end 89end
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-----------------------------------------------------------------------------
91local get_hdrs = function(sock, hdrs) 102function 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
120end 131end
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-----------------------------------------------------------------------------
130local try_getchunked = function(sock, receive_cb) 142function 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-----------------------------------------------------------------------------
178local try_getbylength = function(sock, length, receive_cb) 200function 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-----------------------------------------------------------------------------
206local try_getuntilclosed = function(sock, receive_cb) 228function 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-----------------------------------------------------------------------------
235local try_getbody = function(sock, resp_hdrs, receive_cb) 257function 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
248end 270end
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 280function 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-----------------------------------------------------------------------------
265local 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
286end 282end
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-----------------------------------------------------------------------------
297local try_sendindirect = function(data, send_cb, chunk, size) 293function 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-----------------------------------------------------------------------------
328local send_request = function(sock, method, path, req_hdrs, req_body_cb) 324function 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
356end 352end
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-----------------------------------------------------------------------------
366function has_respbody(method, code) 362function 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
371end 367end
372 368
373----------------------------------------------------------------------------- 369-----------------------------------------------------------------------------
374-- We need base64 convertion routines for Basic Authentication Scheme
375-----------------------------------------------------------------------------
376dofile("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-----------------------------------------------------------------------------
386local fill_hdrs = function(hdrs, parsed) 377function 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
400end 390end
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 401function Private.should_authorize(request, parsed, response)
416----------------------------------------------------------------------------- 402 -- if there has been an authorization attempt, it must have failed
417function 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
456end 407end
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
461dofile("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-----------------------------------------------------------------------------
418function 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)
429end
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 439function 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)
477function 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
492end 445end
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 456function 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)
469end
470
505----------------------------------------------------------------------------- 471-----------------------------------------------------------------------------
506function 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-----------------------------------------------------------------------------
478function 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
508end 485end
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-----------------------------------------------------------------------------
523function http_getindirect(url, resp_body_cb, req_hdrs, stay) 507function 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()
525end 551end
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-----------------------------------------------------------------------------
571function 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
588end
589
540----------------------------------------------------------------------------- 590-----------------------------------------------------------------------------
541function 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-----------------------------------------------------------------------------
600function 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
543end 607end
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-----------------------------------------------------------------------------
559function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) 620function 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
562end 628end