Moonshark/modules/http.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