182 lines
3.2 KiB
Plaintext
182 lines
3.2 KiB
Plaintext
--:Minify:--
|
|
local http = {}
|
|
|
|
local syscall = syscall
|
|
|
|
local function parseUrl(url)
|
|
local proto, host, path =
|
|
url:match(
|
|
"^(https?://)([^/]+)(/.*)$"
|
|
)
|
|
|
|
if not proto then
|
|
proto, host =
|
|
url:match(
|
|
"^(https?://)([^/]+)$"
|
|
)
|
|
|
|
path = "/"
|
|
end
|
|
|
|
if not proto then
|
|
return nil, "EINVAL"
|
|
end
|
|
|
|
return host, path
|
|
end
|
|
|
|
local function buildRequest(req)
|
|
local host, path = parseUrl(req.url)
|
|
|
|
if not host then
|
|
return nil, path
|
|
end
|
|
|
|
local method =
|
|
req.method or "GET"
|
|
|
|
local body =
|
|
req.body or ""
|
|
|
|
local headers =
|
|
table.deepcopy(
|
|
req.headers or {}
|
|
)
|
|
|
|
headers.Host =
|
|
headers.Host or host
|
|
|
|
headers.Connection =
|
|
headers.Connection or "close"
|
|
|
|
if body ~= "" then
|
|
headers["Content-Length"] =
|
|
tostring(#body)
|
|
end
|
|
|
|
local out = {
|
|
method ..
|
|
" " ..
|
|
path ..
|
|
" HTTP/1.1"
|
|
}
|
|
|
|
for k, v in pairs(headers) do
|
|
out[#out + 1] =
|
|
tostring(k) ..
|
|
": " ..
|
|
tostring(v)
|
|
end
|
|
|
|
out[#out + 1] = ""
|
|
out[#out + 1] = body
|
|
|
|
return table.concat(out, "\r\n")
|
|
end
|
|
|
|
local function parseResponse(response)
|
|
local headerBlock, body = response:match("^(.-)\r\n\r\n(.*)$")
|
|
if not headerBlock then
|
|
return nil, "invalid HTTP response"
|
|
end
|
|
|
|
local headers = {}
|
|
|
|
-- Parse status line
|
|
local statusLine, rest = headerBlock:match("^(.-)\r\n(.*)$")
|
|
if not statusLine then
|
|
statusLine = headerBlock
|
|
rest = ""
|
|
end
|
|
|
|
local version, code, message = statusLine:match("^(HTTP/%d+%.%d+)%s+(%d+)%s+(.*)$")
|
|
|
|
-- Parse headers
|
|
for line in (rest .. "\r\n"):gmatch("(.-)\r\n") do
|
|
local key, value = line:match("^([^:]+):%s*(.*)$")
|
|
if key then
|
|
headers[key:lower()] = value
|
|
end
|
|
end
|
|
|
|
return {
|
|
code=tonumber(code),
|
|
message=message,
|
|
headers=headers,
|
|
body=body
|
|
}
|
|
end
|
|
|
|
function http.request(req)
|
|
local raw, err =
|
|
buildRequest(req)
|
|
|
|
if not raw then
|
|
return nil, err
|
|
end
|
|
|
|
local fd =
|
|
syscall.socket()
|
|
|
|
if not fd then
|
|
return nil, "ESOCKET"
|
|
end
|
|
|
|
local host, path = parseUrl(req.url)
|
|
|
|
if not host then
|
|
syscall.close(fd)
|
|
return nil, path
|
|
end
|
|
|
|
local proto = req.url:match("^(https?://)")
|
|
|
|
local base = proto .. host
|
|
|
|
local ok, err = syscall.connect(fd, base)
|
|
|
|
if not ok then
|
|
syscall.close(fd)
|
|
return nil, err
|
|
end
|
|
|
|
local ok2, err2 = syscall.write(fd, raw)
|
|
|
|
if not ok2 then
|
|
syscall.close(fd)
|
|
return nil, err2
|
|
end
|
|
|
|
local out = {}
|
|
|
|
while true do
|
|
local chunk = syscall.read(fd, 4096)
|
|
if chunk == "" then
|
|
break
|
|
end
|
|
|
|
out[#out+1] = chunk
|
|
end
|
|
|
|
syscall.close(fd)
|
|
return parseResponse(table.concat(out))
|
|
end
|
|
|
|
function http.get(url, headers)
|
|
return http.request({
|
|
url = url,
|
|
method = "GET",
|
|
headers = headers
|
|
})
|
|
end
|
|
|
|
function http.post(url, body, headers)
|
|
return http.request({
|
|
url = url,
|
|
method = "POST",
|
|
body = body,
|
|
headers = headers
|
|
})
|
|
end
|
|
|
|
return http |