lua http 源码
时间: 2023-12-03 13:04:54 浏览: 163
lua的源代码
5星 · 资源好评率100%
以下是 Lua 5.1 版本的 http 模块源码,可以用来实现 HTTP 客户端请求:
```lua
-----------------------------------------------------------------------------
-- LuaSocket module for HTTP/1.1 client support
-- Diego Nehab, et al.
-- 04/11/2007
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket")
local url = require("socket.url")
module("socket.http")
-- the default user agent string
USERAGENT = "LuaSocket 2.0.2"
-- timeout values
TIMEOUT = 60
TIMEOUT_CONNECT = socket._TIMEOUT
-- code 204 is no content, but the server sent a response header
-- code 304 is not modified, so there's no need to resend the content
-- code 205 is a reset content request, we must accept the new content
-- code 206 is a partial content response, we must adjust our range
-- code 301 is a redirect, we must look for the location header
-- code 302 is a redirect, but some buggy servers send content along
-- code 303 is a redirect, but with a get method
-- code 307 is a redirect, but we must keep the method
-- code 401 is a authentication request, we must resend with proper creds
-- code 407 is a proxy authentication request, same as 401
NOCONTENT_CODES = "204 304 205 206"
REDIRECT_CODES = "301 302 303 307"
AUTHREQUIRED_CODES = "401 407"
DEFAULT_REDIRECT_TIMES = 5
-- the default port for each protocol
PORT = { http = 80, https = 443, }
-- get a connection object
local function get_connection(u, redir)
local proxy = base._PROXY or socket._PROXY
local conn = {
try = socket.tcp(),
proxy = proxy and url.parse(proxy),
ssl = u.scheme == "https",
live = true,
redirected = false,
redirectcount = 0,
redirectstring = "",
host = u.host,
port = u.port or PORT[u.scheme],
method = "GET",
url = u,
sink = nil,
headers = {
["user-agent"] = USERAGENT,
["host"] = u.host,
},
source = socket.source("close-when-done", conn.try)
}
if conn.proxy then
conn.host = conn.proxy.host
conn.port = conn.proxy.port
conn.headers["host"] = u.authority
end
if redir then
conn.redirected = true
conn.redirectcount = redir.redirectcount + 1
conn.redirectstring = redir.redirectstring.."\n"..u.request
end
return conn
end
-- close a connection
local function close_connection(c)
if c.try then c.try:close() end
c.try = nil
c.live = nil
end
-- receive a line from a connection or a sink
local function receive(fd, pat, t)
local st, chunk
local buffer = {}
local receive_chunk = fd.receive or fd.read or fd
t = t or TIMEOUT
repeat
st, chunk = receive_chunk(fd, pat)
if st then
buffer[#buffer + 1] = chunk
else
return nil, chunk
end
until string.find(buffer[#buffer] or "", pat, nil, true) or st == nil
return table.concat(buffer)
end
-- send data through a connection or a source
local function send(fd, data)
if not fd.send then
if type(fd) ~= "function" then
error("invalid send source")
end
fd(data)
else
fd:send(data)
end
end
-- convert headers to a string
local function headers_to_string(headers)
local buffer = {}
for field, value in pairs(headers) do
buffer[#buffer + 1] = string.format("%s: %s", field, value)
end
buffer[#buffer + 1] = ""
return table.concat(buffer, "\r\n")
end
-- convert headers from a string to a table
local function headers_from_string(header_string)
local headers = {}
local pos = 1
local eol = string.find(header_string, "\n", pos, true)
while eol do
local line = string.sub(header_string, pos, eol - 1)
line = string.gsub(line, "[\r\n]+$", "")
pos = eol + 1
eol = string.find(header_string, "\n", pos, true)
if line ~= "" then
local field, value = string.match(line, "^(.-):%s*(.*)$")
if field then
field = string.lower(field)
if headers[field] then
headers[field] = headers[field]..", "..value
else
headers[field] = value
end
end
else
break
end
end
return headers
end
-- perform a generic HTTP request
local function request(req)
local u = url.parse(req.url)
local c = get_connection(u, req.redirection)
if not c.try then return nil, "unable to connect to "..u.host end
c.try:settimeout(TIMEOUT_CONNECT, "t")
local res = { }
local pat = "^(.-)\r?\n"
-- send request line
local reqline = string.format("%s %s HTTP/1.1", req.method, u.path or "/")
if u.query then reqline = reqline.."?"..u.query end
send(c.try, string.format("%s\r\n", reqline))
-- add headers
if req.source then
c.headers["transfer-encoding"] = "chunked"
c.headers["connection"] = "close"
c.headers["expect"] = "100-continue"
end
for i, header in ipairs(req.headers) do
local name, value = string.match(header, "^(.-):%s*(.*)$")
if name then
c.headers[string.lower(name)] = value
end
end
if not c.headers["host"] then
c.headers["host"] = u.authority
end
send(c.try, headers_to_string(c.headers))
send(c.try, "\r\n")
-- send request body
if req.source then
local source = req.source
while true do
local chunk = source()
if not chunk then
send(c.try, "0\r\n\r\n")
break
end
send(c.try, string.format("%x\r\n", string.len(chunk)))
send(c.try, chunk)
send(c.try, "\r\n")
end
end
c.try:settimeout(TIMEOUT, "t")
-- receive response
local status
local headers = {}
local body
status = receive(c.try, pat)
if status then
local ver, code, message = string.match(status, "^(%S+)%s+(%S+)%s+(.-)\r?$")
if ver and code and message then
status = {
major = tonumber(string.match(ver, "HTTP/(%d)%.%d")),
minor = tonumber(string.match(ver, "HTTP/%d%.(%d)")),
code = tonumber(code),
message = message
}
-- receive headers
local header_string, err = receive(c.try, "\r?\n\r?\n")
if header_string then
headers = headers_from_string(header_string)
-- handle 100 Continue responses
if status.code == 100 then
status, headers, body = request(req)
-- handle 300 redirects
elseif string.find(REDIRECT_CODES, code, 1, true) then
local location = headers.location
if location then
location = url.absolute(u, location)
if req.redirection then
if req.redirection.redirectcount >= DEFAULT_REDIRECT_TIMES then
return nil, "too many redirections"
end
if req.redirection.redirectstring:find(location.request, 1, true) then
return nil, "infinite redirection loop"
end
else
req.redirection = {
redirectcount = 0,
redirectstring = req.url.request,
}
end
req.url = location
close_connection(c)
return request(req)
end
-- handle 401 and 407 authentication requests
elseif string.find(AUTHREQUIRED_CODES, code, 1, true) then
if req.auth and c.headers.authorization then
return nil, "invalid authentication credentials"
end
local auth = headers["www-authenticate"]
or headers["proxy-authenticate"]
if auth then
local realm = string.match(auth, "realm=\"([^\"]*)\"")
if not realm then
realm = string.match(auth, "realm=([^,]*)")
end
if realm then
local user, password = req.auth(realm)
if user then
c.headers.authorization =
socket.try(socket.url.build({
scheme = "basic",
user = user,
password = password
}))
close_connection(c)
return request(req)
end
end
end
end
-- get response body
local length = tonumber(headers["content-length"])
if headers["transfer-encoding"] == "chunked" then
local decoder = socket.protect(function(chunk)
local size = tonumber(chunk, 16)
if size == 0 then return "" end
return receive(c.try, string.format("^([^\r\n]*)\r?\n(.*)\r?\n.{0,%d}$", size - 1), TIMEOUT)
end)
body = socket.sink("into-string")
while true do
local chunk, err = decoder()
if err or chunk == "" then break end
body(chunk)
end
elseif length then
body = receive(c.try, string.format("^%(.{%d})$", length), TIMEOUT)
elseif headers.connection == "close" then
body = receive(c.try, nil, TIMEOUT)
end
end
else
status = nil
end
if not status then
close_connection(c)
return nil, "invalid server response"
end
res.status = status
res.headers = headers
res.body = body
res.request = req
return res
else
close_connection(c)
return nil, "unable to receive status line"
end
end
-- HTTP/1.1 methods
function request_uri(u, req)
req = req or {}
req.method = req.method or "GET"
req.headers = req.headers or {}
req.url = url.absolute(u, req.url)
return request(req)
end
function request_string(u, s)
local req = {
method = "GET",
headers = {},
url = u,
source = socket.source("string", s),
}
req.headers["content-length"] = string.len(s)
return request(req)
end
function request_file(u, f)
local req = {
method = "PUT",
headers = {},
url = u,
source = socket.source("file", f),
}
req.headers["content-length"] = socket.filesize(f)
return request(req)
end
-- HTTP/1.0.0 methods
function get(u, headers)
return request_uri(u, { method = "GET", headers = headers or {} })
end
function post(u, body, headers)
return request_uri(u, {
method = "POST",
headers = headers or {},
source = socket.source("string", body),
})
end
function put(u, body, headers)
return request_uri(u, {
method = "PUT",
headers = headers or {},
source = socket.source("string", body),
})
end
function delete(u, headers)
return request_uri(u, { method = "DELETE", headers = headers or {} })
end
function options(u, headers)
return request_uri(u, { method = "OPTIONS", headers = headers or {} })
end
function trace(u, headers)
return request_uri(u, { method = "TRACE", headers = headers or {} })
end
-- exports
_headers_to_string = headers_to_string
_headers_from_string = headers_from_string
```
该模块提供了一系列 HTTP 请求方法,可以根据需要进行调用。其中,`request_uri` 方法可以实现对 URL 的请求,`get`、`post`、`put`、`delete`、`options`、`trace` 方法则分别对应了 HTTP 协议中的常见请求方法。
阅读全文