moonbridge

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

Impressum / About Us