aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2001-07-29 03:51:36 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2001-07-29 03:51:36 +0000
commit3143c58e0fe839e849aca3b29e0928bd9c1df6a5 (patch)
tree118cacf8df04e81eb1d132dc06217f6eb0b6668f /src
parent0cc37c4b41d8762a62a2939ac100ef8b77205c44 (diff)
downloadluasocket-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.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