moonbridge

view example_application.lua @ 123:20e0d4f51381

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

Impressum / About Us