-- server.lua -- Simple HTTP server in Lua using LuaSocket -- Install dependency: luarocks install luasocket local socket = require("socket") -- ─── Configuration ──────────────────────────────────────────────────────────── local HOST = "127.0.0.1" local PORT = 8080 -- ─── In-memory data store ───────────────────────────────────────────────────── local messages = {} -- stores messages sent from the client -- ─── Helper: parse the request body from raw HTTP request ───────────────────── local function parse_body(raw) -- Body follows the blank line after headers local body = raw:match("\r\n\r\n(.*)") return body or "" end -- ─── Helper: URL-decode a string ────────────────────────────────────────────── local function url_decode(str) str = str:gsub("+", " ") str = str:gsub("%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) return str end -- ─── Helper: parse application/x-www-form-urlencoded body ──────────────────── local function parse_form(body) local params = {} for key, value in body:gmatch("([^&=]+)=([^&=]*)") do params[url_decode(key)] = url_decode(value) end return params end -- ─── Helper: encode a Lua table as a simple JSON object ─────────────────────── local function to_json(tbl) local parts = {} for k, v in pairs(tbl) do local val if type(v) == "string" then val = string.format('"%s"', v:gsub('"', '\\"')) elseif type(v) == "number" then val = tostring(v) elseif type(v) == "boolean" then val = tostring(v) else val = '"[unsupported]"' end table.insert(parts, string.format('"%s": %s', k, val)) end return "{ " .. table.concat(parts, ", ") .. " }" end -- ─── Helper: encode a list of messages as a JSON array ──────────────────────── local function messages_to_json() local items = {} for _, msg in ipairs(messages) do table.insert(items, to_json(msg)) end return "[" .. table.concat(items, ", ") .. "]" end -- ─── HTTP response builders ──────────────────────────────────────────────────── local function http_response(status, content_type, body) return string.format( "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n%s", status, content_type, #body, body ) end local function ok_json(body) return http_response("200 OK", "application/json", body) end local function ok_html(body) return http_response("200 OK", "text/html", body) end local function not_found() return http_response("404 Not Found", "text/plain", "404 Not Found") end -- ─── HTML page served to the browser ───────────────────────────────────────── local HTML = [[ Lua Server Demo

▶ Lua HTTP Server

Bidirectional client ↔ server communication demo

Send a Message

Server Message Log

Server Info

Fetching…
]] -- ─── Route handler ──────────────────────────────────────────────────────────── local start_time = os.time() local request_count = 0 local function handle(raw_request) request_count = request_count + 1 -- Parse method and path from the request line local method, path = raw_request:match("^(%u+) (/[^ ]*) HTTP") method = method or "GET" path = path or "/" -- GET / → serve the HTML page if method == "GET" and path == "/" then return ok_html(HTML) -- GET /messages → return all stored messages as JSON elseif method == "GET" and path == "/messages" then return ok_json(messages_to_json()) -- GET /info → return server metadata as JSON elseif method == "GET" and path == "/info" then local info = { host = HOST, port = tostring(PORT), uptime = tostring(os.time() - start_time), requests = tostring(request_count), lua_version = _VERSION, } return ok_json(to_json(info)) -- POST /send → accept a message from the client elseif method == "POST" and path == "/send" then local body = parse_body(raw_request) local params = parse_form(body) local name = params["name"] or "Anonymous" local text = params["message"] or "" if text == "" then return ok_json('{ "ok": false, "error": "empty message" }') end -- Store the message table.insert(messages, { name = name, text = text, time = os.date("%H:%M:%S"), }) print(string.format("[POST /send] %s: %s", name, text)) return ok_json('{ "ok": true }') else return not_found() end end -- ─── Main server loop ───────────────────────────────────────────────────────── local server = assert(socket.bind(HOST, PORT)) server:settimeout(0) -- non-blocking accept print(string.format("Lua HTTP server running at http://%s:%d/", HOST, PORT)) print("Press Ctrl-C to stop.\n") while true do local client = server:accept() if client then client:settimeout(5) -- Read the full request (stop at blank line for GET, read body for POST) local raw = {} local content_length = 0 -- Read headers while true do local line, err = client:receive("*l") if not line or line == "" then break end table.insert(raw, line) -- Capture Content-Length so we can read the body local cl = line:match("^[Cc]ontent%-[Ll]ength:%s*(%d+)") if cl then content_length = tonumber(cl) end end local header_str = table.concat(raw, "\r\n") .. "\r\n\r\n" -- Read body if present local body_str = "" if content_length > 0 then body_str = client:receive(content_length) or "" end local full_request = header_str .. body_str local response = handle(full_request) client:send(response) client:close() end socket.sleep(0.01) -- prevent busy-loop end