moonbridge
changeset 121:4997e742c81c
Added multiuser chat to example
author | bsw |
---|---|
date | Sat Apr 11 00:10:45 2015 +0200 (2015-04-11) |
parents | 74ec80b721b9 |
children | 3bbcd75eefcd |
files | example_application.lua example_chat.html example_webpage.css |
line diff
1.1 --- a/example_application.lua Fri Apr 10 13:17:29 2015 +0200 1.2 +++ b/example_application.lua Sat Apr 11 00:10:45 2015 +0200 1.3 @@ -7,18 +7,159 @@ 1.4 1.5 -- preparation before forking: 1.6 local documents = {} 1.7 -for i, document_name in ipairs{"example_webpage.html", "example_webpage.css"} do 1.8 +for i, document_name in ipairs{"example_webpage.html", "example_chat.html", "example_webpage.css"} do 1.9 local file = assert(io.open(document_name)) 1.10 documents[document_name] = file:read("*a") -- store file contents in memory 1.11 file:close() 1.12 end 1.13 1.14 +local function chat_server(request) 1.15 + 1.16 + 1.17 + local listener = assert(moonbridge_io.tcplisten("localhost", 8081)) 1.18 + local clients = {} 1.19 + local io_listener = { [listener] = true } 1.20 + local io_writer = {} 1.21 + local sessions = {} 1.22 + local msgs = {} 1.23 + local last_msg = 0 1.24 + 1.25 + while true do 1.26 + 1.27 + local new_conn = listener:accept_nb() 1.28 + 1.29 + if new_conn then 1.30 + clients[new_conn] = { 1.31 + read_buffer = "" 1.32 + } 1.33 + io_listener[new_conn] = true 1.34 + end 1.35 + 1.36 + for conn, client in pairs(clients) do 1.37 + 1.38 + repeat 1.39 + local line = conn:read_nb(nil, "\n") 1.40 + if not line then 1.41 + clients[conn] = nil 1.42 + io_listener[conn] = nil 1.43 + 1.44 + elseif line ~= "" then 1.45 + local line, terminator = line:match("([^\n]*)(\n?)") 1.46 + 1.47 + if terminator ~= "\n" then 1.48 + client.read_buffer = client.read_buffer .. line 1.49 + else 1.50 + local line = client.read_buffer .. line 1.51 + client.read_buffer = "" 1.52 + 1.53 + if not client.type then 1.54 + if line == "events" then 1.55 + client.type = "events" 1.56 + client.session = "sesam" .. math.random(10000000,99999999) 1.57 + client.name = "user".. math.random(10000000,99999999) 1.58 + client.last_msg = 0 1.59 + client.send_session = true 1.60 + client.send_name = true 1.61 + client.last_msg = #msgs 1.62 + sessions[client.session] = conn 1.63 + io_listener[conn] = nil 1.64 + else 1.65 + client.type = "chat" 1.66 + if sessions[line] then 1.67 + client.session = line 1.68 + io_listener[conn] = true 1.69 + else 1.70 + conn:close() 1.71 + clients[conn] = nil 1.72 + end 1.73 + end 1.74 + else 1.75 + if client.type == "chat" then 1.76 + local event_client = clients[sessions[client.session]] 1.77 + local command, arg = line:match("([^:]+):(.*)") 1.78 + if not command then 1.79 + elseif command == "NAME" then 1.80 + local name = arg 1.81 + local success 1.82 + repeat 1.83 + success = true 1.84 + for conn2, client2 in pairs(clients) do 1.85 + if client2.name == name then 1.86 + name = name .. math.random(0,9) 1.87 + success = false 1.88 + break 1.89 + end 1.90 + end 1.91 + until success 1.92 + last_msg = last_msg + 1 1.93 + msgs[last_msg] = { 1.94 + name = event_client.name, 1.95 + msg = "is now known as " .. name 1.96 + } 1.97 + event_client.name = name 1.98 + event_client.send_name = true 1.99 + elseif command == "MSG" then 1.100 + if #arg > 0 then 1.101 + last_msg = last_msg + 1 1.102 + msgs[last_msg] = { 1.103 + name = event_client.name, 1.104 + msg = arg 1.105 + } 1.106 + end 1.107 + end 1.108 + end 1.109 + 1.110 + end 1.111 + end 1.112 + end 1.113 + until not line or line == "" 1.114 + end 1.115 + 1.116 + for conn, client in pairs(clients) do 1.117 + if client.type == "events" then 1.118 + if client.send_session then 1.119 + assert(conn:write_nb("SESSION:" .. client.session .. "\n")) 1.120 + client.send_session = false 1.121 + end 1.122 + if client.send_name then 1.123 + assert(conn:write_nb("NAME:" .. client.name .. "\n")) 1.124 + client.send_name = false 1.125 + end 1.126 + if client.last_msg < last_msg then 1.127 + for i = client.last_msg + 1, last_msg do 1.128 + assert(conn:write_nb("MSG:" .. msgs[i].name .. " " .. msgs[i].msg .. "\n")) 1.129 + end 1.130 + client.last_msg = last_msg 1.131 + end 1.132 + assert(conn:write_nb("TIME:" .. os.time() .. "\n")) 1.133 + local bytes_left = assert(conn:flush_nb()) 1.134 + if bytes_left > 0 then 1.135 + io_writer[conn] = true 1.136 + else 1.137 + io_writer[conn] = false 1.138 + end 1.139 + end 1.140 + 1.141 + end 1.142 + 1.143 + moonbridge_io.poll(io_listener, io_writer) 1.144 + 1.145 + end 1.146 + 1.147 +end 1.148 + 1.149 +listen{ 1.150 + { proto = "interval", delay = 1 }, 1.151 + connect = chat_server, 1.152 + max_fork = 1 1.153 +} 1.154 + 1.155 listen{ 1.156 -- listen to a tcp version 4 socket 1.157 - { proto = "tcp4", port = 8080, localhost = true }, 1.158 + { proto = "tcp4", port = 8080, localhost = false }, 1.159 1.160 -- listen to a tcp version 6 socket 1.161 - { proto = "tcp6", port = 8080, localhost = true }, 1.162 + --{ proto = "tcp6", port = 8080, localhost = false }, 1.163 1.164 -- listen to a unix domain socket 1.165 --{ proto = "local", path = 'socket' }, 1.166 @@ -30,7 +171,7 @@ 1.167 pre_fork = 1, -- number of forks 1.168 1.169 -- minimum number of processes 1.170 - min_fork = 2, -- number of forks 1.171 + min_fork = 4, -- number of forks 1.172 1.173 -- maximum number of processes (hard limit) 1.174 max_fork = 16, -- number of forks 1.175 @@ -79,6 +220,22 @@ 1.176 request:send_status("303 See Other") 1.177 request:send_header("Location", "http://" .. request.headers_value.host .. "/example_webpage.html") 1.178 1.179 + elseif request.path == "chat" then 1.180 + request:send_status("200 OK") 1.181 + request:send_header("Content-Type", "text/chat; charset=UTF-8") 1.182 + request:send_data("MULTIUSERCHAT:protocol version 1\n") 1.183 + 1.184 + local conn = assert(moonbridge_io.tcpconnect("localhost", 8081)) 1.185 + 1.186 + conn:write("events\n") 1.187 + conn:flush() 1.188 + 1.189 + while true do 1.190 + local line = conn:read(nil, "\n") 1.191 + request:send_data(line) 1.192 + request:flush() 1.193 + end 1.194 + 1.195 else 1.196 local document_name = request.path 1.197 local document_extension = string.match(document_name, "%.([^.])$") 1.198 @@ -139,6 +296,37 @@ 1.199 request:send_data("<p>Submitted comment: ", http.encode_html(request.post_params.comment), "</p>\n") 1.200 request:send_data("</body>\n</html>\n") 1.201 1.202 + elseif request.path == "chat_send" then 1.203 + local conn = assert(moonbridge_io.tcpconnect("localhost", 8081)) 1.204 + local body = request.body 1.205 + local session 1.206 + for line in body:gmatch("[^\r\n]+") do 1.207 + local command, arg = line:match("([^:]+):(.*)") 1.208 + if not command then 1.209 + -- TODO error handling 1.210 + return 1.211 + elseif command == "SESSION" then 1.212 + session = arg 1.213 + else 1.214 + if not session then 1.215 + -- TODO error handling 1.216 + return 1.217 + end 1.218 + conn:write(session .. "\n") 1.219 + if command == "NAME" then 1.220 + local name = arg:gsub(" ", "_") 1.221 + conn:write("NAME:" .. name .. "\n") 1.222 + elseif command == "MSG" then 1.223 + local msg = arg 1.224 + conn:write("MSG:" .. msg .. "\n") 1.225 + end 1.226 + end 1.227 + end 1.228 + conn:flush() 1.229 + conn:close() 1.230 + request:send_status("200 OK") 1.231 + request:send_header("Content-Type", "text/xml; chatset=UTF-8") 1.232 + 1.233 else 1.234 error_response("404 Not Found") 1.235
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/example_chat.html Sat Apr 11 00:10:45 2015 +0200 2.3 @@ -0,0 +1,134 @@ 2.4 +<html lang="en"> 2.5 + <head> 2.6 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 2.7 + <link href="example_webpage.css" rel="stylesheet" type="text/css"> 2.8 + <title>Moonbridge Network Server for Lua Applications – Example Application</title> 2.9 + </head> 2.10 + <body> 2.11 + <h1>Moonbridge Network Server for Lua – Example Application</h1> 2.12 + <h2>Multiuser chat</h2> 2.13 + <div id="status">Connecting to server...</div> 2.14 + <div id="chat"> 2.15 + </div> 2.16 + <form action="#" onsubmit="chatSend(); return false;"> 2.17 + Message: <textarea id="msg" name="message" cols="80" rows="3"></textarea><br /> 2.18 + Name: <input type="text" name="name" id="name"> 2.19 + <input type="submit"> 2.20 + Current server time: <span id="time">...</span> 2.21 + </form> 2.22 + 2.23 + <script> 2.24 + 2.25 + function xmlhttprpc() { 2.26 + var req; 2.27 + try { 2.28 + req = new XMLHttpRequest(); 2.29 + } catch(e) { try { 2.30 + req = new ActiveXObject("Microsoft.XMLHTTP"); 2.31 + } catch(e) { try { 2.32 + req = new ActiveXObject("Msxml2.XMLHTTP"); 2.33 + } catch(e) { } } } 2.34 + 2.35 + return req; 2.36 + } 2.37 + 2.38 + var chatName; 2.39 + var chatSession; 2.40 + 2.41 + function chatPrint(name, msg) { 2.42 + var chatEl = document.getElementById("chat"); 2.43 + var el = document.createElement("div"); 2.44 + el.className = "line"; 2.45 + var nameEl = document.createElement("span"); 2.46 + nameEl.className = "name"; 2.47 + var msgEl = document.createElement("span"); 2.48 + msgEl.className = "msg"; 2.49 + nameEl.innerHTML = name; 2.50 + msgEl.innerHTML = msg; 2.51 + el.appendChild(nameEl); 2.52 + el.appendChild(msgEl); 2.53 + chatEl.appendChild(el); 2.54 + } 2.55 + 2.56 + function chatSetSession(session) { 2.57 + chatSession = session; 2.58 + document.getElementById("status").innerHTML = "connected"; 2.59 + document.getElementById("msg").focus(); 2.60 + } 2.61 + 2.62 + function chatSetName(name) { 2.63 + var nameEl = document.getElementById("name"); 2.64 + nameEl.value = name; 2.65 + chatName = name; 2.66 + document.getElementById("msg").focus(); 2.67 + } 2.68 + 2.69 + function chatSetTime(time) { 2.70 + var timeEl = document.getElementById("time"); 2.71 + timeEl.innerHTML = time; 2.72 + } 2.73 + 2.74 + function chatSend() { 2.75 + var req = xmlhttprpc(); 2.76 + req.open('POST', 'chat_send', true); 2.77 + req.onreadystatechange = function() { console.log(req.responseText);}; 2.78 + var name = document.getElementById("name").value; 2.79 + var msg = document.getElementById("msg").value; 2.80 + var datagram = "SESSION:" + chatSession + "\n" 2.81 + if (name != chatName) { 2.82 + datagram += "NAME:" + name + "\n"; 2.83 + } 2.84 + datagram += "MSG:" + msg.replace(/\n/g, "<br />") + "\n"; 2.85 + req.send(datagram); 2.86 + document.getElementById("msg").value = ""; 2.87 + } 2.88 + 2.89 + function chat() { 2.90 + 2.91 + document.getElementById("msg").focus(); 2.92 + 2.93 + var req = xmlhttprpc(); 2.94 + req.open('GET', 'chat', true); 2.95 + 2.96 + var processedChars = 0; 2.97 + 2.98 + req.onreadystatechange = function () { 2.99 + 2.100 + if (req.readyState == 3 || req.readyStateChange == 4) { 2.101 + var re = new RegExp("([^\r\n]+)\r?\n", "g") 2.102 + var unprocessedResponseText = req.responseText.substring(processedChars); 2.103 + while ((line = re.exec(unprocessedResponseText)) !== null) { 2.104 + var line = line[1]; 2.105 + processedChars += line.length + 1; 2.106 + var parts = line.split(":"); 2.107 + var command = parts[0]; 2.108 + var arg = parts.slice(1).join(':'); 2.109 + if (command == "MULTIUSERCHAT") { 2.110 + } else if (command == "SESSION") { 2.111 + chatSetSession(arg); 2.112 + } else if (command == "NAME") { 2.113 + chatSetName(arg); 2.114 + } else if (command == "TIME") { 2.115 + chatSetTime(arg); 2.116 + } else if (command == "MSG") { 2.117 + var parts = arg.split(" "); 2.118 + var from = parts[0]; 2.119 + var msg = parts.slice(1).join(" "); 2.120 + chatPrint(from, msg); 2.121 + } else { 2.122 + chatPrint("SERVER" + arg) 2.123 + } 2.124 + } 2.125 + } 2.126 + 2.127 + 2.128 + } 2.129 + req.send(); 2.130 + } 2.131 + 2.132 + chat(); 2.133 + 2.134 + </script> 2.135 + 2.136 + </body> 2.137 +</html>
3.1 --- a/example_webpage.css Fri Apr 10 13:17:29 2015 +0200 3.2 +++ b/example_webpage.css Sat Apr 11 00:10:45 2015 +0200 3.3 @@ -29,3 +29,16 @@ 3.4 padding: 0.5ex 0.5em; 3.5 } 3.6 3.7 +#chat { 3.8 + background: #fff; 3.9 + margin-bottom: 1ex; 3.10 + padding: 0.5ex 0.5em; 3.11 +} 3.12 + 3.13 +#chat .line .name { 3.14 + margin-right: 0.5em; 3.15 + color: #777; 3.16 +} 3.17 + 3.18 +#chat .line .msg { 3.19 +} 3.20 \ No newline at end of file