-- 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