-- furt-lua/src/main.lua -- Pure Lua HTTP-Server for Furt API-Gateway -- Dragons@Work Digital Sovereignty Project local socket = require("socket") local cjson = require("cjson") -- Load configuration local config = require("config.server") -- HTTP-Server Module local FurtServer = {} function FurtServer:new() local instance = { server = nil, port = config.port or 8080, host = config.host or "127.0.0.1", routes = {} } setmetatable(instance, self) self.__index = self return instance end -- Add route handler function FurtServer:add_route(method, path, handler) if not self.routes[method] then self.routes[method] = {} end self.routes[method][path] = handler end -- Parse HTTP request function FurtServer:parse_request(client) local request_line = client:receive() if not request_line then return nil end -- Parse request line: "POST /v1/mail/send HTTP/1.1" local method, path, protocol = request_line:match("(%w+) (%S+) (%S+)") if not method then return nil end -- Parse headers local headers = {} local content_length = 0 while true do local line = client:receive() if not line or line == "" then break end local key, value = line:match("([^:]+): (.+)") if key and value then headers[key:lower()] = value if key:lower() == "content-length" then content_length = tonumber(value) or 0 end end end -- Parse body local body = "" if content_length > 0 then body = client:receive(content_length) end return { method = method, path = path, protocol = protocol, headers = headers, body = body, content_length = content_length } end -- Create HTTP response function FurtServer:create_response(status, data, content_type) content_type = content_type or "application/json" local body = "" if type(data) == "table" then body = cjson.encode(data) else body = tostring(data or "") end local response = string.format( "HTTP/1.1 %d %s\r\n" .. "Content-Type: %s\r\n" .. "Content-Length: %d\r\n" .. "Connection: close\r\n" .. "Server: Furt-Lua/1.0\r\n" .. "\r\n%s", status, self:get_status_text(status), content_type, #body, body ) return response end -- Get HTTP status text function FurtServer:get_status_text(status) local status_texts = { [200] = "OK", [400] = "Bad Request", [404] = "Not Found", [405] = "Method Not Allowed", [500] = "Internal Server Error" } return status_texts[status] or "Unknown" end -- Handle client request function FurtServer:handle_client(client) local request = self:parse_request(client) if not request then local response = self:create_response(400, {error = "Invalid request"}) client:send(response) return end print(string.format("[%s] %s %s", os.date("%Y-%m-%d %H:%M:%S"), request.method, request.path)) -- Route handling local handler = nil if self.routes[request.method] and self.routes[request.method][request.path] then handler = self.routes[request.method][request.path] end if handler then local success, result = pcall(handler, request) if success then client:send(result) else print("Handler error: " .. tostring(result)) local error_response = self:create_response(500, {error = "Internal server error"}) client:send(error_response) end else local response = self:create_response(404, {error = "Route not found"}) client:send(response) end end -- Start HTTP server function FurtServer:start() self.server = socket.bind(self.host, self.port) if not self.server then error("Failed to bind to " .. self.host .. ":" .. self.port) end print(string.format("Furt HTTP-Server started on %s:%d", self.host, self.port)) print("Press Ctrl+C to stop") while true do local client = self.server:accept() if client then client:settimeout(10) -- 10 second timeout self:handle_client(client) client:close() end end end -- Initialize server and routes local server = FurtServer:new() -- Health check route server:add_route("GET", "/health", function(request) local response_data = { status = "healthy", service = "furt-lua", version = "1.0.0", timestamp = os.time() } return server:create_response(200, response_data) end) -- Test route for development server:add_route("POST", "/test", function(request) local response_data = { message = "Test endpoint working", received_data = request.body, headers_count = 0 } -- Count headers for _ in pairs(request.headers) do response_data.headers_count = response_data.headers_count + 1 end return server:create_response(200, response_data) end) -- Mail service route (placeholder for Week 1) server:add_route("POST", "/v1/mail/send", function(request) -- Basic validation if not request.body or request.body == "" then return server:create_response(400, {error = "No request body"}) end -- Try to parse JSON local success, data = pcall(cjson.decode, request.body) if not success then return server:create_response(400, {error = "Invalid JSON"}) end -- Basic field validation if not data.name or not data.email or not data.message then return server:create_response(400, { error = "Missing required fields", required = {"name", "email", "message"} }) end -- TODO: Implement actual mail sending via SMTP print("Mail request received: " .. data.name .. " <" .. data.email .. ">") local response_data = { success = true, message = "Mail queued for sending", request_id = os.time() .. "-" .. math.random(1000, 9999) } return server:create_response(200, response_data) end) -- Start server server:start()