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 – 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 – 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
|