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.
This commit is contained in:
Diego Nehab 2001-07-29 03:51:36 +00:00
parent 0cc37c4b41
commit 3143c58e0f

View File

@ -1,22 +1,32 @@
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Full HTTP/1.1 client support for the Lua language using the -- Full HTTP/1.1 client support for the Lua language using the
-- LuaSocket 1.2 toolkit. -- LuaSocket 1.4a toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Date: 26/12/2000 -- Date: 26/12/2000
-- Conforming to: RFC 2068 -- Conforming to: RFC 2068
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local Public, Private = {}, {}
HTTP = Public
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Program constants -- Program constants
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- connection timeout in seconds -- connection timeout in seconds
local TIMEOUT = 60 Private.TIMEOUT = 60
-- default port for document retrieval -- default port for document retrieval
local PORT = 80 Private.PORT = 80
-- user agent field sent in request -- user agent field sent in request
local USERAGENT = "LuaSocket 1.3b HTTP 1.1" Private.USERAGENT = "LuaSocket 1.4a"
-- block size used in transfers -- block size used in transfers
local BLOCKSIZE = 8192 Private.BLOCKSIZE = 8192
-----------------------------------------------------------------------------
-- Required libraries
-----------------------------------------------------------------------------
dofile "buffer.lua"
dofile "url.lua"
dofile "encode.lua"
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Tries to get a pattern from the server and closes socket on error -- Tries to get a pattern from the server and closes socket on error
@ -26,9 +36,9 @@ local BLOCKSIZE = 8192
-- data: line received or nil in case of error -- data: line received or nil in case of error
-- err: error message if any -- err: error message if any
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_get = function(...) function Private.try_receive(...)
local sock = arg[1] local sock = arg[1]
local data, err = call(sock.receive, arg) local data, err = call(sock.receive, arg)
if err then if err then
sock:close() sock:close()
return nil, err return nil, err
@ -43,8 +53,8 @@ end
-- Returns -- Returns
-- err: error message if any, nil if successfull -- err: error message if any, nil if successfull
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_send = function(sock, data) function Private.try_send(sock, data)
err = sock:send(data) local err = sock:send(data)
if err then sock:close() end if err then sock:close() end
return err return err
end end
@ -56,13 +66,14 @@ end
-- Returns -- Returns
-- code: integer with status code -- code: integer with status code
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local get_statuscode = function(line) function Private.get_statuscode(line)
local _,_, code = strfind(line, " (%d%d%d) ") local code, _
_, _, code = strfind(line, "HTTP/%d*%.%d* (%d%d%d)")
return tonumber(code) return tonumber(code)
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Receive server reply messages -- Receive server reply messages, parsing status code
-- Input -- Input
-- sock: socket connected to the server -- sock: socket connected to the server
-- Returns -- Returns
@ -70,29 +81,29 @@ end
-- line: full http status line -- line: full http status line
-- err: error message if any -- err: error message if any
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local get_status = function(sock) function Private.receive_status(sock)
local line, err local line, err
line, err = %try_get(sock) line, err = %Private.try_receive(sock)
if not err then return %get_statuscode(line), line if not err then return %Private.get_statuscode(line), line
else return nil, nil, err end else return nil, nil, err end
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Receive and parse responce header fields -- Receive and parse response header fields
-- Input -- Input
-- sock: socket connected to the server -- sock: socket connected to the server
-- hdrs: a table that might already contain headers -- headers: a table that might already contain headers
-- Returns -- Returns
-- hdrs: a table with all headers fields in the form -- headers: a table with all headers fields in the form
-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
-- all name_i are lowercase -- all name_i are lowercase
-- nil and error message in case of error -- nil and error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local get_hdrs = function(sock, hdrs) function Private.receive_headers(sock, headers)
local line, err local line, err
local name, value local name, value, _
-- get first line -- get first line
line, err = %try_get(sock) line, err = %Private.try_receive(sock)
if err then return nil, err end if err then return nil, err end
-- headers go until a blank line is found -- headers go until a blank line is found
while line ~= "" do while line ~= "" do
@ -104,34 +115,35 @@ local get_hdrs = function(sock, hdrs)
end end
name = strlower(name) name = strlower(name)
-- get next line (value might be folded) -- get next line (value might be folded)
line, err = %try_get(sock) line, err = %Private.try_receive(sock)
if err then return nil, err end if err then return nil, err end
-- unfold any folded values -- unfold any folded values
while not err and strfind(line, "^%s") do while not err and strfind(line, "^%s") do
value = value .. line value = value .. line
line, err = %try_get(sock) line, err = %Private.try_receive(sock)
if err then return nil, err end if err then return nil, err end
end end
-- save pair in table -- save pair in table
if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value if headers[name] then headers[name] = headers[name] .. ", " .. value
else hdrs[name] = value end else headers[name] = value end
end end
return hdrs return headers
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Receives a chunked message body -- Receives a chunked message body
-- Input -- Input
-- sock: socket connected to the server -- sock: socket connected to the server
-- headers: header set in which to include trailer headers
-- receive_cb: function to receive chunks -- receive_cb: function to receive chunks
-- Returns -- Returns
-- nil if successfull or an error message in case of error -- nil if successfull or an error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_getchunked = function(sock, receive_cb) function Private.receivebody_bychunks(sock, headers, receive_cb)
local chunk, size, line, err, go, uerr, _ local chunk, size, line, err, go, uerr, _
repeat while 1 do
-- get chunk size, skip extention -- get chunk size, skip extention
line, err = %try_get(sock) line, err = %Private.try_receive(sock)
if err then if err then
local _, uerr = receive_cb(nil, err) local _, uerr = receive_cb(nil, err)
return uerr or err return uerr or err
@ -143,8 +155,10 @@ local try_getchunked = function(sock, receive_cb)
_, uerr = receive_cb(nil, err) _, uerr = receive_cb(nil, err)
return uerr or err return uerr or err
end end
-- was it the last chunk?
if size <= 0 then break end
-- get chunk -- get chunk
chunk, err = %try_get(sock, size) chunk, err = %Private.try_receive(sock, size)
if err then if err then
_, uerr = receive_cb(nil, err) _, uerr = receive_cb(nil, err)
return uerr or err return uerr or err
@ -155,13 +169,21 @@ local try_getchunked = function(sock, receive_cb)
sock:close() sock:close()
return uerr or "aborted by callback" return uerr or "aborted by callback"
end end
-- skip blank line -- skip CRLF on end of chunk
_, err = %try_get(sock) _, err = %Private.try_receive(sock)
if err then if err then
_, uerr = receive_cb(nil, err) _, uerr = receive_cb(nil, err)
return uerr or err return uerr or err
end end
until size <= 0 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
_, uerr = receive_cb(nil, err)
return uerr or err
end
-- let callback know we are done -- let callback know we are done
_, uerr = receive_cb("") _, uerr = receive_cb("")
return uerr return uerr
@ -175,10 +197,10 @@ end
-- Returns -- Returns
-- nil if successfull or an error message in case of error -- nil if successfull or an error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_getbylength = function(sock, length, receive_cb) function Private.receivebody_bylength(sock, length, receive_cb)
local uerr, go local uerr, go
while length > 0 do while length > 0 do
local size = min(%BLOCKSIZE, length) local size = min(%Private.BLOCKSIZE, length)
local chunk, err = sock:receive(size) local chunk, err = sock:receive(size)
if err then if err then
go, uerr = receive_cb(nil, err) go, uerr = receive_cb(nil, err)
@ -203,10 +225,10 @@ end
-- Returns -- Returns
-- nil if successfull or an error message in case of error -- nil if successfull or an error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_getuntilclosed = function(sock, receive_cb) function Private.receivebody_untilclosed(sock, receive_cb)
local err, go, uerr local err, go, uerr
while 1 do while 1 do
local chunk, err = sock:receive(%BLOCKSIZE) local chunk, err = sock:receive(%Private.BLOCKSIZE)
if err == "closed" or not err then if err == "closed" or not err then
go, uerr = receive_cb(chunk) go, uerr = receive_cb(chunk)
if not go then if not go then
@ -227,62 +249,36 @@ end
-- Receives http response body -- Receives http response body
-- Input -- Input
-- sock: socket connected to the server -- sock: socket connected to the server
-- resp_hdrs: response header fields -- headers: response header fields
-- receive_cb: function to receive chunks -- receive_cb: function to receive chunks
-- Returns -- Returns
-- nil if successfull or an error message in case of error -- nil if successfull or an error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_getbody = function(sock, resp_hdrs, receive_cb) function Private.receive_body(sock, headers, receive_cb)
local err local te = headers["transfer-encoding"]
if resp_hdrs["transfer-encoding"] == "chunked" then if te and te ~= "identity" then
-- get by chunked transfer-coding of message body -- get by chunked transfer-coding of message body
return %try_getchunked(sock, receive_cb) return %Private.receivebody_bychunks(sock, headers, receive_cb)
elseif tonumber(resp_hdrs["content-length"]) then elseif tonumber(headers["content-length"]) then
-- get by content-length -- get by content-length
local length = tonumber(resp_hdrs["content-length"]) local length = tonumber(headers["content-length"])
return %try_getbylength(sock, length, receive_cb) return %Private.receivebody_bylength(sock, length, receive_cb)
else else
-- get it all until connection closes -- get it all until connection closes
return %try_getuntilclosed(sock, receive_cb) return %Private.receivebody_untilclosed(sock, receive_cb)
end end
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Parses a url and returns its scheme, user, password, host, port -- Drop http response body
-- and path components, according to RFC 1738
-- Input -- Input
-- url: uniform resource locator of request -- sock: socket connected to the server
-- default: table containing default values to be returned -- headers: response header fields
-- Returns -- Returns
-- table with the following fields: -- nil if successfull or an error message in case of error
-- host: host to connect
-- path: url path
-- port: host port to connect
-- user: user name
-- pass: password
-- scheme: protocol
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local split_url = function(url, default) function Private.drop_body(sock, headers)
-- initialize default parameters return %Private.receive_body(sock, headers, function (c, e) return 1 end)
local parsed = default or {}
-- get scheme
url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
-- get user name and password. both can be empty!
-- moreover, password can be ommited
url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
%parsed.user = u
-- there can be an empty password, but the ':' has to be there
-- or else there is no password
%parsed.pass = nil -- kill default password
if c == ":" then %parsed.pass = p end
end)
-- get host
url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
-- get port if any
url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
-- whatever is left is the path
if url ~= "" then parsed.path = url end
return parsed
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -294,20 +290,20 @@ end
-- Returns -- Returns
-- nil if successfull, or an error message in case of error -- nil if successfull, or an error message in case of error
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local try_sendindirect = function(data, send_cb, chunk, size) function Private.send_indirect(data, send_cb, chunk, size)
local sent, err local sent, err
sent = 0 sent = 0
while 1 do while 1 do
if type(chunk) ~= "string" or type(size) ~= "number" then if type(chunk) ~= "string" or type(size) ~= "number" then
data:close() data:close()
if not chunk and type(size) == "string" then return size if not chunk and type(size) == "string" then return size
else return "invalid callback return" end else return "invalid callback return" end
end end
err = data:send(chunk) err = data:send(chunk)
if err then if err then
data:close() data:close()
return err return err
end end
sent = sent + strlen(chunk) sent = sent + strlen(chunk)
if sent >= size then break end if sent >= size then break end
chunk, size = send_cb() chunk, size = send_cb()
@ -320,243 +316,313 @@ end
-- sock: socket connected to the server -- sock: socket connected to the server
-- method: request method to be used -- method: request method to be used
-- path: url path -- path: url path
-- req_hdrs: request headers to be sent -- headers: request headers to be sent
-- req_body_cb: callback to send request message body -- body_cb: callback to send request message body
-- Returns -- Returns
-- err: nil in case of success, error message otherwise -- err: nil in case of success, error message otherwise
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local send_request = function(sock, method, path, req_hdrs, req_body_cb) function Private.send_request(sock, method, path, headers, body_cb)
local chunk, size, done, err local chunk, size, done, err
-- send request line -- send request line
err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") err = %Private.try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n")
if err then return err end if err then return err end
-- if there is a request message body, add content-length header -- if there is a request message body, add content-length header
if req_body_cb then if body_cb then
chunk, size = req_body_cb() chunk, size = body_cb()
if type(chunk) == "string" and type(size) == "number" then if type(chunk) == "string" and type(size) == "number" then
req_hdrs["content-length"] = tostring(size) headers["content-length"] = tostring(size)
else else
sock:close() sock:close()
if not chunk and type(size) == "string" then return size if not chunk and type(size) == "string" then return size
else return "invalid callback return" end else return "invalid callback return" end
end end
end end
-- send request headers -- send request headers
for i, v in req_hdrs do for i, v in headers do
err = %try_send(sock, i .. ": " .. v .. "\r\n") err = %Private.try_send(sock, i .. ": " .. v .. "\r\n")
if err then return err end if err then return err end
end end
-- mark end of request headers -- mark end of request headers
err = %try_send(sock, "\r\n") err = %Private.try_send(sock, "\r\n")
if err then return err end if err then return err end
-- send request message body, if any -- send request message body, if any
if req_body_cb then if body_cb then
return %try_sendindirect(sock, req_body_cb, chunk, size) return %Private.send_indirect(sock, body_cb, chunk, size)
end end
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Determines if we should read a message body from the server response -- Determines if we should read a message body from the server response
-- Input -- Input
-- method: method used in request -- request: a table with the original request information
-- code: server response status code -- response: a table with the server response information
-- Returns -- Returns
-- 1 if a message body should be processed, nil otherwise -- 1 if a message body should be processed, nil otherwise
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function has_respbody(method, code) function Private.has_body(request, response)
if method == "HEAD" then return nil end if request.method == "HEAD" then return nil end
if code == 204 or code == 304 then return nil end if response.code == 204 or response.code == 304 then return nil end
if code >= 100 and code < 200 then return nil end if response.code >= 100 and response.code < 200 then return nil end
return 1 return 1
end end
-----------------------------------------------------------------------------
-- We need base64 convertion routines for Basic Authentication Scheme
-----------------------------------------------------------------------------
dofile("base64.lua")
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Converts field names to lowercase and adds a few needed headers -- Converts field names to lowercase and adds a few needed headers
-- Input -- Input
-- hdrs: request header fields -- headers: request header fields
-- parsed: parsed url components -- parsed: parsed url components
-- Returns -- Returns
-- lower: a table with the same headers, but with lowercase field names -- lower: a table with the same headers, but with lowercase field names
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local fill_hdrs = function(hdrs, parsed) function Private.fill_headers(headers, parsed)
local lower = {} local lower = {}
hdrs = hdrs or {} headers = headers or {}
for i,v in hdrs do -- set default headers
lower["user-agent"] = %Private.USERAGENT
lower["host"] = parsed.host
-- override with user values
for i,v in headers do
lower[strlower(i)] = v lower[strlower(i)] = v
end end
-- this cannot be overriden
lower["connection"] = "close" lower["connection"] = "close"
lower["host"] = parsed.host
lower["user-agent"] = %USERAGENT
if parsed.user and parsed.pass then -- Basic Authentication
lower["authorization"] = "Basic "..
base64(parsed.user .. ":" .. parsed.pass)
end
return lower return lower
end 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 " ..
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_indirect(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 = URL.build_absolute(request.url,response.headers["location"]),
body_cb = request.body_cb,
headers = request.headers
}
return %Public.request_indirect(redirect, response)
end
-----------------------------------------------------------------------------
-- Computes the request URI from the given 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
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Sends a HTTP request and retrieves the server reply using callbacks to -- Sends a HTTP request and retrieves the server reply using callbacks to
-- send the request body and receive the response body -- send the request body and receive the response body
-- Input -- Input
-- method: "GET", "PUT", "POST" etc -- request: a table with the following fields
-- url: target uniform resource locator -- method: "GET", "PUT", "POST" etc (defaults to "GET")
-- resp_body_cb: response message body receive callback -- url: target uniform resource locator
-- req_hdrs: request headers to send, or nil if none -- user, password: authentication information
-- req_body_cb: request message body send callback, or nil if none -- headers: request headers to send, or nil if none
-- stay: should we refrain from following a server redirect message? -- 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 -- Returns
-- resp_hdrs: response header fields received, or nil if failed -- response: a table with the following fields:
-- resp_line: server response status line, or nil if failed -- headers: response header fields received, or nil if failed
-- err: error message, or nil if successfull -- 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 http_requestindirect(method, url, resp_body_cb, req_hdrs, function Public.request_indirect(request, response)
req_body_cb, stay)
local sock, err
local resp_hdrs
local resp_line, resp_code
-- get url components -- get url components
local parsed = %split_url(url, {port = %PORT, path ="/"}) local parsed = URL.parse(request.url, {port = %Private.PORT, path ="/"})
-- methods are case sensitive -- explicit authentication info overrides that given by the url
method = strupper(method) 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 -- fill default headers
req_hdrs = %fill_hdrs(req_hdrs, parsed) request.headers = %Private.fill_headers(request.headers, parsed)
-- try connection -- try to connect to server
sock, err = connect(parsed.host, parsed.port) local sock
if not sock then return nil, nil, err end sock, response.error = connect(parsed.host, parsed.port)
-- set connection timeout if not sock then return response end
sock:timeout(%TIMEOUT) -- set connection timeout so that we do not hang forever
-- send request sock:timeout(%Private.TIMEOUT)
err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) -- send request message
if err then return nil, nil, err end response.error = %Private.send_request(sock, request.method,
-- get server message %Private.request_uri(parsed), request.headers, request.body_cb)
resp_code, resp_line, err = %get_status(sock) if response.error then return response end
if err then return nil, nil, err end -- get server response message
-- deal with reply response.code, response.status, response.error =
resp_hdrs, err = %get_hdrs(sock, {}) %Private.receive_status(sock)
if err then return nil, line, err end if response.error then return response end
-- did we get a redirect? should we automatically retry? -- receive all headers
if not stay and (resp_code == 301 or resp_code == 302) and response.headers, response.error = %Private.receive_headers(sock, {})
(method == "GET" or method == "HEAD") then 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() sock:close()
return http_requestindirect(method, resp_hdrs["location"], return %Private.redirect(request, response)
resp_body_cb, req_hdrs, req_body_cb, stay) elseif %Private.should_authorize(request, parsed, response) then
end %Private.drop_body(sock, response.headers)
-- get response message body if status and method combination allow one sock:close()
if has_respbody(method, resp_code) then return %Private.authorize(request, parsed, response)
err = %try_getbody(sock, resp_hdrs, resp_body_cb) elseif %Private.has_body(request, response) then
if err then return resp_hdrs, resp_line, err end response.error = %Private.receive_body(sock, response.headers,
response.body_cb)
if response.error then return response end
sock:close()
return response
end end
sock:close() sock:close()
return resp_hdrs, resp_line
end end
-----------------------------------------------------------------------------
-- We need fast concatenation routines for direct requests
-----------------------------------------------------------------------------
dofile("buffer.lua")
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Sends a HTTP request and retrieves the server reply -- Sends a HTTP request and retrieves the server reply
-- Input -- Input
-- method: "GET", "PUT", "POST" etc -- request: a table with the following fields
-- url: target uniform resource locator -- method: "GET", "PUT", "POST" etc (defaults to "GET")
-- req_hdrs: request headers to send, or nil if none -- url: target url, i.e. the document to be retrieved
-- req_body: request message body as a string, or nil if none -- user, password: authentication information
-- stay: should we refrain from following a server redirect message? -- 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 -- Returns
-- resp_body: response message body, or nil if failed -- response: a table with the following fields:
-- resp_hdrs: response header fields received, or nil if failed -- body: response message body, or nil if failed
-- resp_line: server response status line, or nil if failed -- headers: response header fields, or nil if failed
-- err: error message, or nil if successfull -- status: server response status line, or nil if failed
-- code: server response status code, or nil if failed
-- error: error message if any
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function http_request(method, url, req_hdrs, req_body, stay) function Public.request(request)
local resp_hdrs, resp_line, err local response = {}
local req_body_cb = function() if request.body then
return %req_body, strlen(%req_body) request.body_cb = function()
return %request.body, strlen(%request.body)
end
end end
local resp_body = { buf = buf_create() } local auxiliar = { buf = buf_create() }
local resp_body_cb = function(chunk, err) response.body_cb = function(chunk, err)
if not chunk then %resp_body.buf = nil end if not chunk then %auxiliar.buf = nil end
buf_addstring(%resp_body.buf, chunk) buf_addstring(%auxiliar.buf, chunk)
return 1 return 1
end end
if not req_body then req_body_cb = nil end %Public.request_indirect(request, response)
resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, response.body = buf_getresult(auxiliar.buf)
req_hdrs, req_body_cb, stay) response.body_cb = nil
return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err return response
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Retrieves a URL by the method "GET" -- Retrieves a URL by the method "GET"
-- Input -- Input
-- url: target uniform resource locator -- url: target url, i.e. the document to be retrieved
-- req_hdrs: request headers to send, or nil if none
-- stay: should we refrain from following a server redirect message?
-- Returns -- Returns
-- resp_body: response message body, or nil if failed -- body: response message body, or nil if failed
-- resp_hdrs: response header fields received, or nil if failed -- headers: response header fields received, or nil if failed
-- resp_line: server response status line, or nil if failed -- status: server response status line, or nil if failed
-- err: error message, or nil if successfull -- error: error message if any
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function http_get(url, req_hdrs, stay) function Public.get(url)
return http_request("GET", url, req_hdrs, stay) local response = %Public.request {
end method = "GET",
url = url
----------------------------------------------------------------------------- }
-- Retrieves a URL by the method "GET" return response.body, response.headers,
-- Input response.status, response.error
-- url: target uniform resource locator
-- resp_body_cb: response message body receive callback
-- req_hdrs: request headers to send, or nil if none
-- stay: should we refrain from following a server redirect message?
-- Returns
-- resp_body: response message body, or nil if failed
-- resp_hdrs: response header fields received, or nil if failed
-- resp_line: server response status line, or nil if failed
-- err: error message, or nil if successfull
-----------------------------------------------------------------------------
function http_getindirect(url, resp_body_cb, req_hdrs, stay)
return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay)
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Retrieves a URL by the method "POST" -- Retrieves a URL by the method "POST"
-- Input -- Input
-- method: "GET", "PUT", "POST" etc -- url: target url, i.e. the document to be retrieved
-- url: target uniform resource locator -- body: request message body, or nil if none
-- req_hdrs: request headers to send, or nil if none
-- req_body: request message body, or nil if none
-- stay: should we refrain from following a server redirect message?
-- Returns -- Returns
-- resp_body: response message body, or nil if failed -- body: response message body, or nil if failed
-- resp_hdrs: response header fields received, or nil if failed -- headers: response header fields received, or nil if failed
-- resp_line: server response status line, or nil if failed -- status: server response status line, or nil if failed
-- err: error message, or nil if successfull -- error: error message, or nil if successfull
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function http_post(url, req_body, req_hdrs, stay) function Public.post(url, body)
return http_request("POST", url, req_hdrs, req_body, stay) local response = %Public.request {
end method = "POST",
url = url,
----------------------------------------------------------------------------- body = body
-- Retrieves a URL by the method "POST" }
-- Input return response.body, response.headers,
-- url: target uniform resource locator response.status, response.error
-- resp_body_cb: response message body receive callback
-- req_body_cb: request message body send callback
-- req_hdrs: request headers to send, or nil if none
-- stay: should we refrain from following a server redirect message?
-- Returns
-- resp_body: response message body, or nil if failed
-- resp_hdrs: response header fields received, or nil if failed
-- resp_line: server response status line, or nil if failed
-- err: error message, or nil if successfull
-----------------------------------------------------------------------------
function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay)
return http_requestindirect("GET", url, resp_body_cb, req_hdrs,
req_body_cb, stay)
end end