277 lines
6.1 KiB
Lua
277 lines
6.1 KiB
Lua
-- modules/http.lua - HTTP server with Go-based routing
|
|
|
|
local http = {}
|
|
|
|
-- ======================================================================
|
|
-- HTTP SERVER
|
|
-- ======================================================================
|
|
|
|
local HTTPServer = {}
|
|
HTTPServer.__index = HTTPServer
|
|
|
|
function HTTPServer:listen(addr)
|
|
local success, err = moonshark.http_server_listen(self.id, addr)
|
|
if not success then
|
|
error("Failed to start server: " .. (err or "unknown error"))
|
|
end
|
|
self.addr = addr
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:stop()
|
|
local success, err = moonshark.http_server_stop(self.id)
|
|
if not success then
|
|
error("Failed to stop server: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:get(path, handler)
|
|
if type(handler) ~= "function" then
|
|
error("Handler must be a function")
|
|
end
|
|
|
|
local success, err = moonshark.http_server_get(self.id, path, handler)
|
|
if not success then
|
|
error("Failed to add GET route: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:post(path, handler)
|
|
if type(handler) ~= "function" then
|
|
error("Handler must be a function")
|
|
end
|
|
|
|
local success, err = moonshark.http_server_post(self.id, path, handler)
|
|
if not success then
|
|
error("Failed to add POST route: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:put(path, handler)
|
|
if type(handler) ~= "function" then
|
|
error("Handler must be a function")
|
|
end
|
|
|
|
local success, err = moonshark.http_server_put(self.id, path, handler)
|
|
if not success then
|
|
error("Failed to add PUT route: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:patch(path, handler)
|
|
if type(handler) ~= "function" then
|
|
error("Handler must be a function")
|
|
end
|
|
|
|
local success, err = moonshark.http_server_patch(self.id, path, handler)
|
|
if not success then
|
|
error("Failed to add PATCH route: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:delete(path, handler)
|
|
if type(handler) ~= "function" then
|
|
error("Handler must be a function")
|
|
end
|
|
|
|
local success, err = moonshark.http_server_delete(self.id, path, handler)
|
|
if not success then
|
|
error("Failed to add DELETE route: " .. (err or "unknown error"))
|
|
end
|
|
return self
|
|
end
|
|
|
|
function HTTPServer:isRunning()
|
|
return moonshark.http_server_is_running(self.id)
|
|
end
|
|
|
|
-- Handle cleanup when server is garbage collected
|
|
function HTTPServer:__gc()
|
|
if self:isRunning() then
|
|
pcall(function() self:stop() end)
|
|
end
|
|
end
|
|
|
|
-- ======================================================================
|
|
-- HTTP MODULE FUNCTIONS
|
|
-- ======================================================================
|
|
|
|
function http.newServer(options)
|
|
options = options or {}
|
|
|
|
local serverID = moonshark.http_create_server()
|
|
if not serverID then
|
|
error("Failed to create HTTP server")
|
|
end
|
|
|
|
local server = setmetatable({
|
|
id = serverID,
|
|
addr = nil
|
|
}, HTTPServer)
|
|
|
|
return server
|
|
end
|
|
|
|
-- Convenience function to create and start server in one call
|
|
function http.listen(addr, handler)
|
|
local server = http.newServer()
|
|
|
|
if handler then
|
|
server:get("/*", handler)
|
|
end
|
|
|
|
return server:listen(addr)
|
|
end
|
|
|
|
-- Clean up all servers
|
|
function http.cleanup()
|
|
moonshark.http_cleanup_servers()
|
|
end
|
|
|
|
-- ======================================================================
|
|
-- REQUEST/RESPONSE HELPERS
|
|
-- ======================================================================
|
|
|
|
http.status = {
|
|
OK = 200,
|
|
CREATED = 201,
|
|
NO_CONTENT = 204,
|
|
MOVED_PERMANENTLY = 301,
|
|
FOUND = 302,
|
|
NOT_MODIFIED = 304,
|
|
BAD_REQUEST = 400,
|
|
UNAUTHORIZED = 401,
|
|
FORBIDDEN = 403,
|
|
NOT_FOUND = 404,
|
|
METHOD_NOT_ALLOWED = 405,
|
|
CONFLICT = 409,
|
|
INTERNAL_SERVER_ERROR = 500,
|
|
NOT_IMPLEMENTED = 501,
|
|
BAD_GATEWAY = 502,
|
|
SERVICE_UNAVAILABLE = 503
|
|
}
|
|
|
|
-- Create a response object
|
|
function http.response(status, body, headers)
|
|
return {
|
|
status = status or http.status.OK,
|
|
body = body or "",
|
|
headers = headers or {}
|
|
}
|
|
end
|
|
|
|
-- Create JSON response
|
|
function http.json(data, status)
|
|
local json_str = moonshark.json_encode(data)
|
|
return {
|
|
status = status or http.status.OK,
|
|
body = json_str,
|
|
headers = {
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
}
|
|
end
|
|
|
|
-- Create text response
|
|
function http.text(text, status)
|
|
return {
|
|
status = status or http.status.OK,
|
|
body = tostring(text),
|
|
headers = {
|
|
["Content-Type"] = "text/plain"
|
|
}
|
|
}
|
|
end
|
|
|
|
-- Create HTML response
|
|
function http.html(html, status)
|
|
return {
|
|
status = status or http.status.OK,
|
|
body = tostring(html),
|
|
headers = {
|
|
["Content-Type"] = "text/html"
|
|
}
|
|
}
|
|
end
|
|
|
|
-- Redirect response
|
|
function http.redirect(location, status)
|
|
return {
|
|
status = status or http.status.FOUND,
|
|
body = "",
|
|
headers = {
|
|
["Location"] = location
|
|
}
|
|
}
|
|
end
|
|
|
|
-- Error response
|
|
function http.error(message, status)
|
|
return http.json({
|
|
error = message or "Internal Server Error"
|
|
}, status or http.status.INTERNAL_SERVER_ERROR)
|
|
end
|
|
|
|
-- ======================================================================
|
|
-- UTILITY FUNCTIONS
|
|
-- ======================================================================
|
|
|
|
-- Parse query string
|
|
function http.parseQuery(queryString)
|
|
local params = {}
|
|
if not queryString or queryString == "" then
|
|
return params
|
|
end
|
|
|
|
for pair in queryString:gmatch("[^&]+") do
|
|
local key, value = pair:match("([^=]+)=?(.*)")
|
|
if key then
|
|
key = http.urlDecode(key)
|
|
value = http.urlDecode(value or "")
|
|
params[key] = value
|
|
end
|
|
end
|
|
|
|
return params
|
|
end
|
|
|
|
-- URL decode
|
|
function http.urlDecode(str)
|
|
if not str then return "" end
|
|
str = str:gsub("+", " ")
|
|
str = str:gsub("%%(%x%x)", function(hex)
|
|
return string.char(tonumber(hex, 16))
|
|
end)
|
|
return str
|
|
end
|
|
|
|
-- URL encode
|
|
function http.urlEncode(str)
|
|
if not str then return "" end
|
|
str = str:gsub("([^%w%-%.%_%~])", function(c)
|
|
return string.format("%%%02X", string.byte(c))
|
|
end)
|
|
return str
|
|
end
|
|
|
|
-- Parse cookies
|
|
function http.parseCookies(cookieHeader)
|
|
local cookies = {}
|
|
if not cookieHeader then return cookies end
|
|
|
|
for pair in cookieHeader:gmatch("[^;]+") do
|
|
local key, value = pair:match("^%s*([^=]+)=?(.*)")
|
|
if key then
|
|
cookies[key:match("^%s*(.-)%s*$")] = value and value:match("^%s*(.-)%s*$") or ""
|
|
end
|
|
end
|
|
|
|
return cookies
|
|
end
|
|
|
|
return http |