local tcp = require "tcp"
local function parse_uri(uri)
local path, query_str, fragment = uri:match("([^?#]*)%??([^#]*)#?(.*)")
local query = {}
if #query_str > 0 then
for pair in (query_str.."&"):gmatch("([^&]*)&") do
local key, val = pair:match("([^=]*)=(.*)")
query[key] = val
end
end
return path, query, fragment
end
local function parse_request(data)
local req = {payload="", headers={}}
local stage = "status"
for line in data:gmatch("(.-)\r?\n") do
if stage == "status" then
local target, protocol
req.method, target, protocol = line:match("(%S*) (%S*) (%S*)")
assert(protocol == "HTTP/1.1")
req.path, req.query, req.fragment = parse_uri(target)
stage = "header"
elseif stage == "header" then
if line == "" then
stage = "payload"
else
local key, value = line:match("([^:]*):%s*(.*)")
req.headers[key:lower()] = value
end
else -- payload
req.payload = req.payload..line.."\n"
end
end
return req
end
-- cookies is a sequence of tables with the following keys:
-- key, val -> cookie entry (required)
-- path -> cookie scope, e.g., /
-- age -> cookie expiration time in seconds
local function build_cookie_data(cookies)
local data = ""
for i, c in ipairs(cookies or {}) do
local line = ("Set-Cookie: %s=%s; HttpOnly"):format(c.key, c.val)
if c.path ~= nil then
line = line .. "; Path=" .. c.path
end
if c.age ~= nil then
line = line .. "; Max-Age=" .. c.age
end
data = data .. line .. "\r\n"
end
return data
end
local function build_response(data, status, reason, cookies)
if status == nil then
status = 200
reason = "OK"
end
local cookie_data = build_cookie_data(cookies)
local fmt = "HTTP/1.1 %03d %s\r\n%s\r\n%s"
return fmt:format(status, reason, cookie_data, data)
end
local HTTP = {}
HTTP.__index = HTTP
function HTTP:run(port)
self.tcp:init(port)
self.tcp:run()
end
local function new_http()
local obj = setmetatable({}, HTTP)
obj.tcp = tcp.new_tcp(1000, 200)
function obj.tcp:process(datain)
local req = parse_request(datain)
local dataout, status, reason, cookies = obj:process(req)
if dataout == nil then
return nil
end
return build_response(dataout, status, reason, cookies)
end
return obj
end
return {new_http=new_http}