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

", status, "

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

Moonbridge Network Server for Lua – Example Application

\n") jbe@0: request:send_data("

POST request successful

\n") jbe@0: request:send_data('\n\n\n') jbe@0: for i, file in ipairs(files) do jbe@0: request:send_data("") jbe@0: request:send_data("") jbe@0: request:send_data("") jbe@0: request:send_data('") jbe@0: request:send_data("\n") jbe@0: end jbe@0: 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") jbe@0: request:send_data("

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

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