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