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