moonbridge

annotate example_application.lua @ 121:4997e742c81c

Added multiuser chat to example
author bsw
date Sat Apr 11 00:10:45 2015 +0200 (2015-04-11)
parents 7014436d88ea
children 20e0d4f51381
rev   line source
jbe@0 1 -- Moonbridge example application
jbe@0 2 -- invoke with ./moonbridge example_application.lua
jbe@33 3 --
jbe@33 4 -- see helloworld.lua for a simpler example
jbe@0 5
jbe@4 6 local http = require "moonbridge_http"
jbe@0 7
jbe@26 8 -- preparation before forking:
jbe@13 9 local documents = {}
bsw@121 10 for i, document_name in ipairs{"example_webpage.html", "example_chat.html", "example_webpage.css"} do
jbe@15 11 local file = assert(io.open(document_name))
jbe@26 12 documents[document_name] = file:read("*a") -- store file contents in memory
jbe@13 13 file:close()
jbe@13 14 end
jbe@0 15
bsw@121 16 local function chat_server(request)
bsw@121 17
bsw@121 18
bsw@121 19 local listener = assert(moonbridge_io.tcplisten("localhost", 8081))
bsw@121 20 local clients = {}
bsw@121 21 local io_listener = { [listener] = true }
bsw@121 22 local io_writer = {}
bsw@121 23 local sessions = {}
bsw@121 24 local msgs = {}
bsw@121 25 local last_msg = 0
bsw@121 26
bsw@121 27 while true do
bsw@121 28
bsw@121 29 local new_conn = listener:accept_nb()
bsw@121 30
bsw@121 31 if new_conn then
bsw@121 32 clients[new_conn] = {
bsw@121 33 read_buffer = ""
bsw@121 34 }
bsw@121 35 io_listener[new_conn] = true
bsw@121 36 end
bsw@121 37
bsw@121 38 for conn, client in pairs(clients) do
bsw@121 39
bsw@121 40 repeat
bsw@121 41 local line = conn:read_nb(nil, "\n")
bsw@121 42 if not line then
bsw@121 43 clients[conn] = nil
bsw@121 44 io_listener[conn] = nil
bsw@121 45
bsw@121 46 elseif line ~= "" then
bsw@121 47 local line, terminator = line:match("([^\n]*)(\n?)")
bsw@121 48
bsw@121 49 if terminator ~= "\n" then
bsw@121 50 client.read_buffer = client.read_buffer .. line
bsw@121 51 else
bsw@121 52 local line = client.read_buffer .. line
bsw@121 53 client.read_buffer = ""
bsw@121 54
bsw@121 55 if not client.type then
bsw@121 56 if line == "events" then
bsw@121 57 client.type = "events"
bsw@121 58 client.session = "sesam" .. math.random(10000000,99999999)
bsw@121 59 client.name = "user".. math.random(10000000,99999999)
bsw@121 60 client.last_msg = 0
bsw@121 61 client.send_session = true
bsw@121 62 client.send_name = true
bsw@121 63 client.last_msg = #msgs
bsw@121 64 sessions[client.session] = conn
bsw@121 65 io_listener[conn] = nil
bsw@121 66 else
bsw@121 67 client.type = "chat"
bsw@121 68 if sessions[line] then
bsw@121 69 client.session = line
bsw@121 70 io_listener[conn] = true
bsw@121 71 else
bsw@121 72 conn:close()
bsw@121 73 clients[conn] = nil
bsw@121 74 end
bsw@121 75 end
bsw@121 76 else
bsw@121 77 if client.type == "chat" then
bsw@121 78 local event_client = clients[sessions[client.session]]
bsw@121 79 local command, arg = line:match("([^:]+):(.*)")
bsw@121 80 if not command then
bsw@121 81 elseif command == "NAME" then
bsw@121 82 local name = arg
bsw@121 83 local success
bsw@121 84 repeat
bsw@121 85 success = true
bsw@121 86 for conn2, client2 in pairs(clients) do
bsw@121 87 if client2.name == name then
bsw@121 88 name = name .. math.random(0,9)
bsw@121 89 success = false
bsw@121 90 break
bsw@121 91 end
bsw@121 92 end
bsw@121 93 until success
bsw@121 94 last_msg = last_msg + 1
bsw@121 95 msgs[last_msg] = {
bsw@121 96 name = event_client.name,
bsw@121 97 msg = "is now known as " .. name
bsw@121 98 }
bsw@121 99 event_client.name = name
bsw@121 100 event_client.send_name = true
bsw@121 101 elseif command == "MSG" then
bsw@121 102 if #arg > 0 then
bsw@121 103 last_msg = last_msg + 1
bsw@121 104 msgs[last_msg] = {
bsw@121 105 name = event_client.name,
bsw@121 106 msg = arg
bsw@121 107 }
bsw@121 108 end
bsw@121 109 end
bsw@121 110 end
bsw@121 111
bsw@121 112 end
bsw@121 113 end
bsw@121 114 end
bsw@121 115 until not line or line == ""
bsw@121 116 end
bsw@121 117
bsw@121 118 for conn, client in pairs(clients) do
bsw@121 119 if client.type == "events" then
bsw@121 120 if client.send_session then
bsw@121 121 assert(conn:write_nb("SESSION:" .. client.session .. "\n"))
bsw@121 122 client.send_session = false
bsw@121 123 end
bsw@121 124 if client.send_name then
bsw@121 125 assert(conn:write_nb("NAME:" .. client.name .. "\n"))
bsw@121 126 client.send_name = false
bsw@121 127 end
bsw@121 128 if client.last_msg < last_msg then
bsw@121 129 for i = client.last_msg + 1, last_msg do
bsw@121 130 assert(conn:write_nb("MSG:" .. msgs[i].name .. " " .. msgs[i].msg .. "\n"))
bsw@121 131 end
bsw@121 132 client.last_msg = last_msg
bsw@121 133 end
bsw@121 134 assert(conn:write_nb("TIME:" .. os.time() .. "\n"))
bsw@121 135 local bytes_left = assert(conn:flush_nb())
bsw@121 136 if bytes_left > 0 then
bsw@121 137 io_writer[conn] = true
bsw@121 138 else
bsw@121 139 io_writer[conn] = false
bsw@121 140 end
bsw@121 141 end
bsw@121 142
bsw@121 143 end
bsw@121 144
bsw@121 145 moonbridge_io.poll(io_listener, io_writer)
bsw@121 146
bsw@121 147 end
bsw@121 148
bsw@121 149 end
bsw@121 150
bsw@121 151 listen{
bsw@121 152 { proto = "interval", delay = 1 },
bsw@121 153 connect = chat_server,
bsw@121 154 max_fork = 1
bsw@121 155 }
bsw@121 156
jbe@0 157 listen{
jbe@0 158 -- listen to a tcp version 4 socket
bsw@121 159 { proto = "tcp4", port = 8080, localhost = false },
jbe@0 160
jbe@0 161 -- listen to a tcp version 6 socket
bsw@121 162 --{ proto = "tcp6", port = 8080, localhost = false },
jbe@0 163
jbe@0 164 -- listen to a unix domain socket
jbe@0 165 --{ proto = "local", path = 'socket' },
jbe@0 166
jbe@0 167 -- execute the listener regularly (without incoming connection)
jbe@0 168 --{ proto = "interval", name = "myint", delay = 10, strict = false },
jbe@0 169
jbe@0 170 -- desired number of spare (idle) processes
jbe@0 171 pre_fork = 1, -- number of forks
jbe@0 172
jbe@0 173 -- minimum number of processes
bsw@121 174 min_fork = 4, -- number of forks
jbe@0 175
jbe@0 176 -- maximum number of processes (hard limit)
jbe@0 177 max_fork = 16, -- number of forks
jbe@0 178
jbe@0 179 -- delay between creation of spare processes
jbe@61 180 fork_delay = 0.25, -- seconds
jbe@0 181
jbe@0 182 -- delay before retry of failed process creation
jbe@0 183 fork_error_delay = 2, -- seconds
jbe@0 184
jbe@0 185 -- delay between destruction of excessive spare processes
jbe@0 186 exit_delay = 60, -- seconds
jbe@0 187
jbe@0 188 -- idle time after a fork gets terminated
jbe@0 189 idle_timeout = 0, -- seconds (0 for no timeout)
jbe@0 190
jbe@0 191 -- maximum memory consumption before process gets terminated
jbe@23 192 --memory_limit = 1024*1024, -- bytes
jbe@0 193
jbe@11 194 -- preparation of process (executed after fork)
jbe@0 195 prepare = function()
jbe@26 196 -- e.g. open database connection
jbe@0 197 end,
jbe@0 198
jbe@0 199 -- connection handler
jbe@0 200 connect = http.generate_handler(
jbe@0 201 {
jbe@0 202 static_headers = {"Server: Moonbridge Example Server"},
jbe@54 203 request_body_size_limit = 16*1024*1024*1024, -- allow big file uploads
jbe@115 204 request_idle_timeout = 330, -- 5 minutes and 30 seconds after which an idle connection will be closed
jbe@115 205 request_header_timeout = 30, -- request headers must be sent within 30 seconds after first byte was received
jbe@54 206 timeout = 1800 -- request body and response must be sent within 30 minutes
jbe@0 207 },
jbe@0 208 function(request)
jbe@0 209
jbe@33 210 local function error_response(status)
jbe@33 211 request:send_status(status)
jbe@33 212 request:send_header("Content-Type", "text/html")
jbe@33 213 request:send_data("<html>\n<head><title>", status, "</title></head>\n<body><h1>", status, "</h1></body>\n</html>\n")
jbe@33 214 request:finish()
jbe@23 215 end
jbe@23 216
jbe@0 217 if request.method == "GET" or request.method == "HEAD" then
jbe@0 218
jbe@10 219 if request.path == "" then
jbe@0 220 request:send_status("303 See Other")
jbe@0 221 request:send_header("Location", "http://" .. request.headers_value.host .. "/example_webpage.html")
jbe@0 222
bsw@121 223 elseif request.path == "chat" then
bsw@121 224 request:send_status("200 OK")
bsw@121 225 request:send_header("Content-Type", "text/chat; charset=UTF-8")
bsw@121 226 request:send_data("MULTIUSERCHAT:protocol version 1\n")
bsw@121 227
bsw@121 228 local conn = assert(moonbridge_io.tcpconnect("localhost", 8081))
bsw@121 229
bsw@121 230 conn:write("events\n")
bsw@121 231 conn:flush()
bsw@121 232
bsw@121 233 while true do
bsw@121 234 local line = conn:read(nil, "\n")
bsw@121 235 request:send_data(line)
bsw@121 236 request:flush()
bsw@121 237 end
bsw@121 238
jbe@0 239 else
jbe@10 240 local document_name = request.path
jbe@0 241 local document_extension = string.match(document_name, "%.([^.])$")
jbe@26 242 local document = documents[document_name] -- loads file contents from memory
jbe@0 243 if document then
jbe@0 244 request:send_status("200 OK")
jbe@0 245 if document_extension == "html" then
jbe@0 246 request:send_header("Content-Type", "text/html; charset=UTF-8")
jbe@0 247 elseif document_extension == "css" then
jbe@0 248 request:send_header("Content-Type", "text/css; charset=UTF-8")
jbe@0 249 end
jbe@0 250 request:send_data(document)
jbe@0 251 else
jbe@33 252 error_response("404 Not Found")
jbe@0 253 end
jbe@0 254
jbe@0 255 end
jbe@0 256
jbe@0 257 elseif request.method == "POST" then
jbe@0 258
jbe@10 259 if request.path == "post_example" then
jbe@0 260 local files = {}
jbe@0 261 do
jbe@0 262 local file
jbe@0 263 request:stream_post_param("files", function(chunk, field_name, meta)
jbe@0 264 if meta then
jbe@0 265 file = {
jbe@0 266 file_name = meta.file_name,
jbe@0 267 content_type = meta.content_type,
jbe@0 268 length = 0
jbe@0 269 }
jbe@0 270 end
jbe@0 271 if chunk then
jbe@0 272 file.length = file.length + #chunk
jbe@0 273 else
jbe@0 274 files[#files+1] = file
jbe@0 275 end
jbe@0 276 end)
jbe@0 277 end
jbe@0 278
jbe@0 279 request:send_status("200 OK")
jbe@0 280 request:send_header("Content-Type", "text/html; chatset=UTF-8")
jbe@0 281 request:send_data("<html>\n<head>\n")
jbe@0 282 request:send_data('<link href="example_webpage.css" rel="stylesheet" type="text/css">\n')
jbe@0 283 request:send_data("<title>Moonbridge Network Server for Lua Applications &ndash; Example Application</title>\n")
jbe@0 284 request:send_data("</head>\n<body>\n")
jbe@0 285 request:send_data("<h1>Moonbridge Network Server for Lua &ndash; Example Application</h1>\n")
jbe@0 286 request:send_data("<h2>POST request successful</h2>\n")
jbe@0 287 request:send_data('<table>\n<thead><th>File name</th><th>Content type</th><th class="numeric">Bytes received</th></thead>\n<tbody>\n')
jbe@0 288 for i, file in ipairs(files) do
jbe@0 289 request:send_data("<tr>")
jbe@0 290 request:send_data("<td>", http.encode_html(file.file_name or "(unknown)"), "</td>")
jbe@0 291 request:send_data("<td>", http.encode_html(file.content_type or "(unknown)"), "</td>")
jbe@0 292 request:send_data('<td class="numeric">', http.encode_html(tostring(file.length)), "</td>")
jbe@0 293 request:send_data("</tr>\n")
jbe@0 294 end
jbe@0 295 request:send_data("</tbody>\n</table>\n")
jbe@0 296 request:send_data("<p>Submitted comment: ", http.encode_html(request.post_params.comment), "</p>\n")
jbe@0 297 request:send_data("</body>\n</html>\n")
jbe@0 298
bsw@121 299 elseif request.path == "chat_send" then
bsw@121 300 local conn = assert(moonbridge_io.tcpconnect("localhost", 8081))
bsw@121 301 local body = request.body
bsw@121 302 local session
bsw@121 303 for line in body:gmatch("[^\r\n]+") do
bsw@121 304 local command, arg = line:match("([^:]+):(.*)")
bsw@121 305 if not command then
bsw@121 306 -- TODO error handling
bsw@121 307 return
bsw@121 308 elseif command == "SESSION" then
bsw@121 309 session = arg
bsw@121 310 else
bsw@121 311 if not session then
bsw@121 312 -- TODO error handling
bsw@121 313 return
bsw@121 314 end
bsw@121 315 conn:write(session .. "\n")
bsw@121 316 if command == "NAME" then
bsw@121 317 local name = arg:gsub(" ", "_")
bsw@121 318 conn:write("NAME:" .. name .. "\n")
bsw@121 319 elseif command == "MSG" then
bsw@121 320 local msg = arg
bsw@121 321 conn:write("MSG:" .. msg .. "\n")
bsw@121 322 end
bsw@121 323 end
bsw@121 324 end
bsw@121 325 conn:flush()
bsw@121 326 conn:close()
bsw@121 327 request:send_status("200 OK")
bsw@121 328 request:send_header("Content-Type", "text/xml; chatset=UTF-8")
bsw@121 329
jbe@0 330 else
jbe@33 331 error_response("404 Not Found")
jbe@0 332
jbe@23 333 end
jbe@0 334
jbe@0 335 else
jbe@33 336 error_response("405 Method not allowed")
jbe@0 337
jbe@0 338 end
jbe@0 339
jbe@0 340 -- returning false causes termination of current process (and re-forking)
jbe@0 341 return true
jbe@0 342 end),
jbe@0 343
jbe@0 344 -- executed on process termination
jbe@0 345 finish = function()
jbe@26 346 -- e.g. close database connection
jbe@0 347 end
jbe@0 348 }
jbe@0 349

Impressum / About Us