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