-- Moonbridge example application -- invoke with ./moonbridge example_application.lua -- -- see helloworld.lua for a simpler example local http = require "moonbridge_http" -- preparation before forking: local documents = {} for i, document_name in ipairs{"example_webpage.html", "example_chat.html", "example_webpage.css"} do local file = assert(io.open(document_name)) documents[document_name] = file:read("*a") -- store file contents in memory file:close() end local function chat_server(request) local listener = assert(moonbridge_io.tcplisten("localhost", 8081)) local clients = {} local io_listener = { [listener] = true } local io_writer = {} local sessions = {} local msgs = {} local last_msg = 0 while true do local new_conn = listener:accept_nb() if new_conn then clients[new_conn] = { read_buffer = "" } io_listener[new_conn] = true end for conn, client in pairs(clients) do local line = conn:read_nb(nil, "\n") if not line then clients[conn] = nil io_listener[conn] = nil elseif line ~= "" then local line, terminator = line:match("([^\n]*)(\n?)") if terminator ~= "\n" then client.read_buffer = client.read_buffer .. line else local line = client.read_buffer .. line client.read_buffer = "" if not client.type then if line == "events" then client.type = "events" client.session = "sesam" .. math.random(10000000,99999999) client.name = "user".. math.random(10000000,99999999) client.last_msg = 0 client.send_session = true client.send_name = true client.last_msg = #msgs sessions[client.session] = conn io_listener[conn] = nil else client.type = "chat" if sessions[line] then client.session = line io_listener[conn] = true else conn:close() clients[conn] = nil end end else if client.type == "chat" then local event_client = clients[sessions[client.session]] local command, arg = line:match("([^:]+):(.*)") if not command then elseif command == "NAME" then local name = arg local success repeat success = true for conn2, client2 in pairs(clients) do if client2.name == name then name = name .. math.random(0,9) success = false break end end until success last_msg = last_msg + 1 msgs[last_msg] = { name = event_client.name, msg = "is now known as " .. name } event_client.name = name event_client.send_name = true elseif command == "MSG" then if #arg > 0 then last_msg = last_msg + 1 msgs[last_msg] = { name = event_client.name, msg = arg } end end end end end end end for conn, client in pairs(clients) do if client.type == "events" then if client.send_session then assert(conn:write_nb("SESSION:" .. client.session .. "\n")) client.send_session = false end if client.send_name then assert(conn:write_nb("NAME:" .. client.name .. "\n")) client.send_name = false end if client.last_msg < last_msg then for i = client.last_msg + 1, last_msg do assert(conn:write_nb("MSG:" .. msgs[i].name .. " " .. msgs[i].msg .. "\n")) end client.last_msg = last_msg end assert(conn:write_nb("TIME:" .. os.time() .. "\n")) local bytes_left = assert(conn:flush_nb()) if bytes_left > 0 then io_writer[conn] = true else io_writer[conn] = false end end end moonbridge_io.poll(io_listener, io_writer, 5) end end listen{ { proto = "main" }, connect = chat_server } listen{ -- listen to a tcp version 4 socket --{ proto = "tcp", host = "0.0.0.0", port = 8080 }, -- listen to a tcp version 6 socket { proto = "tcp", host = "::", port = 8080}, -- listen to a unix domain socket --{ proto = "local", path = 'socket' }, -- execute the listener regularly (without incoming connection) --{ proto = "interval", name = "myint", delay = 10, strict = false }, -- desired number of spare (idle) processes pre_fork = 1, -- number of forks -- minimum number of processes min_fork = 4, -- number of forks -- maximum number of processes (hard limit) max_fork = 16, -- number of forks -- delay between creation of spare processes fork_delay = 0.25, -- seconds -- delay before retry of failed process creation fork_error_delay = 2, -- seconds -- delay between destruction of excessive spare processes exit_delay = 60, -- seconds -- idle time after a fork gets terminated idle_timeout = 0, -- seconds (0 for no timeout) -- maximum memory consumption before process gets terminated --memory_limit = 1024*1024, -- bytes -- preparation of process (executed after fork) prepare = function() -- e.g. open database connection end, -- connection handler connect = http.generate_handler( { static_headers = {"Server: Moonbridge Example Server"}, request_body_size_limit = 16*1024*1024*1024, -- allow big file uploads request_idle_timeout = 330, -- 5 minutes and 30 seconds after which an idle connection will be closed request_header_timeout = 30, -- request headers must be sent within 30 seconds after first byte was received timeout = 1800 -- request body and response must be sent within 30 minutes }, function(request) local function error_response(status) request:send_status(status) request:send_header("Content-Type", "text/html") request:send_data("\n", status, "\n

", status, "

\n\n") request:finish() end if request.method == "GET" or request.method == "HEAD" then if request.path == "" then request:send_status("303 See Other") request:send_header("Location", "http://" .. request.headers_value.host .. "/example_webpage.html") elseif request.path == "chat" then request:send_status("200 OK") request:send_header("Content-Type", "text/chat; charset=UTF-8") request:send_data("MULTIUSERCHAT:protocol version 1\n") local conn = assert(moonbridge_io.tcpconnect("localhost", 8081)) conn:write("events\n") conn:flush() while true do local line = conn:read(nil, "\n") request:send_data(line) request:flush() end else local document_name = request.path local document_extension = string.match(document_name, "%.([^.])$") local document = documents[document_name] -- loads file contents from memory if document then request:send_status("200 OK") if document_extension == "html" then request:send_header("Content-Type", "text/html; charset=UTF-8") elseif document_extension == "css" then request:send_header("Content-Type", "text/css; charset=UTF-8") end request:send_data(document) else error_response("404 Not Found") end end elseif request.method == "POST" then if request.path == "post_example" then local files = {} do local file request:stream_post_param("files", function(chunk, field_name, meta) if meta then file = { file_name = meta.file_name, content_type = meta.content_type, length = 0 } end if chunk then file.length = file.length + #chunk else files[#files+1] = file end end) end request:send_status("200 OK") request:send_header("Content-Type", "text/html; chatset=UTF-8") request:send_data("\n\n") request:send_data('\n') request:send_data("Moonbridge Network Server for Lua Applications – Example Application\n") request:send_data("\n\n") request:send_data("

Moonbridge Network Server for Lua – Example Application

\n") request:send_data("

POST request successful

\n") request:send_data('\n\n\n') for i, file in ipairs(files) do request:send_data("") request:send_data("") request:send_data("") request:send_data('") request:send_data("\n") end request:send_data("\n
File nameContent typeBytes received
", http.encode_html(file.file_name or "(unknown)"), "", http.encode_html(file.content_type or "(unknown)"), "', http.encode_html(tostring(file.length)), "
\n") request:send_data("

Submitted comment: ", http.encode_html(request.post_params.comment), "

\n") request:send_data("\n\n") elseif request.path == "chat_send" then local conn = assert(moonbridge_io.tcpconnect("localhost", 8081)) local body = request.body local session for line in body:gmatch("[^\r\n]+") do local command, arg = line:match("([^:]+):(.*)") if not command then -- TODO error handling return elseif command == "SESSION" then session = arg else if not session then -- TODO error handling return end conn:write(session .. "\n") if command == "NAME" then local name = arg:gsub(" ", "_") conn:write("NAME:" .. name .. "\n") elseif command == "MSG" then local msg = arg conn:write("MSG:" .. msg .. "\n") end end end conn:flush() conn:close() request:send_status("200 OK") request:send_header("Content-Type", "text/xml; chatset=UTF-8") else error_response("404 Not Found") end else error_response("405 Method not allowed") end -- returning false causes termination of current process (and re-forking) return true end), -- executed on process termination finish = function() -- e.g. close database connection end }