You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
3.6 KiB
Lua

local internal = require "http.internal"
local string = string
local type = type
local httpd = {}
local http_status_msg = {
[100] = "Continue",
[101] = "Switching Protocols",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[300] = "Multiple Choices",
[301] = "Moved Permanently",
[302] = "Found",
[303] = "See Other",
[304] = "Not Modified",
[305] = "Use Proxy",
[307] = "Temporary Redirect",
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Time-out",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Request Entity Too Large",
[414] = "Request-URI Too Large",
[415] = "Unsupported Media Type",
[416] = "Requested range not satisfiable",
[417] = "Expectation Failed",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Time-out",
[505] = "HTTP Version not supported",
}
local function readall(readbytes, bodylimit)
local tmpline = {}
local body = internal.recvheader(readbytes, tmpline, "")
if not body then
return 413 -- Request Entity Too Large
end
local request = assert(tmpline[1])
local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$"
assert(method and url and httpver)
httpver = assert(tonumber(httpver))
if httpver < 1.0 or httpver > 1.1 then
return 505 -- HTTP Version not supported
end
local header = internal.parseheader(tmpline,2,{})
if not header then
return 400 -- Bad request
end
local length = header["content-length"]
if length then
length = tonumber(length)
end
local mode = header["transfer-encoding"]
if mode then
if mode ~= "identity" and mode ~= "chunked" then
return 501 -- Not Implemented
end
end
if mode == "chunked" then
body, header = internal.recvchunkedbody(readbytes, bodylimit, header, body)
if not body then
return 413
end
else
-- identity mode
if length then
if bodylimit and length > bodylimit then
return 413
end
if #body >= length then
body = body:sub(1,length)
else
local padding = readbytes(length - #body)
body = body .. padding
end
end
end
return 200, url, method, header, body
end
function httpd.read_request(...)
local ok, code, url, method, header, body = pcall(readall, ...)
if ok then
return code, url, method, header, body
else
return nil, code
end
end
local function writeall(writefunc, statuscode, bodyfunc, header)
local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "")
writefunc(statusline)
if header then
for k,v in pairs(header) do
if type(v) == "table" then
for _,v in ipairs(v) do
writefunc(string.format("%s: %s\r\n", k,v))
end
else
writefunc(string.format("%s: %s\r\n", k,v))
end
end
end
local t = type(bodyfunc)
if t == "string" then
writefunc(string.format("content-length: %d\r\n\r\n", #bodyfunc))
writefunc(bodyfunc)
elseif t == "function" then
writefunc("transfer-encoding: chunked\r\n")
while true do
local s = bodyfunc()
if s then
if s ~= "" then
writefunc(string.format("\r\n%x\r\n", #s))
writefunc(s)
end
else
writefunc("\r\n0\r\n\r\n")
break
end
end
else
assert(t == "nil")
writefunc("\r\n")
end
end
function httpd.write_response(...)
return pcall(writeall, ...)
end
return httpd