moonbridge

view example_application.lua @ 191:ab12ecb399f1

Signal request body EOF in HTTP module
author jbe
date Fri Jun 19 19:35:23 2015 +0200 (2015-06-19)
parents 89fc89b22e28
children 5adfc36ca73f
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 = "main" },
151 connect = chat_server
152 }
154 listen{
155 -- listen to a tcp version 4 socket
156 --{ proto = "tcp", host = "0.0.0.0", port = 8080 },
158 -- listen to a tcp version 6 socket
159 { proto = "tcp", host = "::", port = 8080},
161 -- listen to a unix domain socket
162 --{ proto = "local", path = 'socket' },
164 -- execute the listener regularly (without incoming connection)
165 --{ proto = "interval", name = "myint", delay = 10, strict = false },
167 -- desired number of spare (idle) processes
168 pre_fork = 1, -- number of forks
170 -- minimum number of processes
171 min_fork = 4, -- number of forks
173 -- maximum number of processes (hard limit)
174 max_fork = 16, -- number of forks
176 -- delay between creation of spare processes
177 fork_delay = 0.25, -- seconds
179 -- delay before retry of failed process creation
180 fork_error_delay = 2, -- seconds
182 -- delay between destruction of excessive spare processes
183 exit_delay = 60, -- seconds
185 -- idle time after a fork gets terminated
186 idle_timeout = 0, -- seconds (0 for no timeout)
188 -- maximum memory consumption before process gets terminated
189 --memory_limit = 1024*1024, -- bytes
191 -- preparation of process (executed after fork)
192 prepare = function()
193 -- e.g. open database connection
194 end,
196 -- connection handler
197 connect = http.generate_handler(
198 {
199 static_headers = {"Server: Moonbridge Example Server"},
200 request_body_size_limit = 16*1024*1024*1024, -- allow big file uploads
201 request_idle_timeout = 330, -- 5 minutes and 30 seconds after which an idle connection will be closed
202 request_header_timeout = 30, -- request headers must be sent within 30 seconds after first byte was received
203 timeout = 1800 -- request body and response must be sent within 30 minutes
204 },
205 function(request)
207 local function error_response(status)
208 request:send_status(status)
209 request:send_header("Content-Type", "text/html")
210 request:send_data("<html>\n<head><title>", status, "</title></head>\n<body><h1>", status, "</h1></body>\n</html>\n")
211 request:finish()
212 end
214 if request.method == "GET" or request.method == "HEAD" then
216 if request.path == "" then
217 request:send_status("303 See Other")
218 request:send_header("Location", "http://" .. request.headers_value.host .. "/example_webpage.html")
220 elseif request.path == "chat" then
221 request:send_status("200 OK")
222 request:send_header("Content-Type", "text/chat; charset=UTF-8")
223 request:send_data("MULTIUSERCHAT:protocol version 1\n")
225 local conn = assert(moonbridge_io.tcpconnect("localhost", 8081))
227 conn:write("events\n")
228 conn:flush()
230 while true do
231 local line = conn:read(nil, "\n")
232 request:send_data(line)
233 request:flush()
234 end
236 else
237 local document_name = request.path
238 local document_extension = string.match(document_name, "%.([^.])$")
239 local document = documents[document_name] -- loads file contents from memory
240 if document then
241 request:send_status("200 OK")
242 if document_extension == "html" then
243 request:send_header("Content-Type", "text/html; charset=UTF-8")
244 elseif document_extension == "css" then
245 request:send_header("Content-Type", "text/css; charset=UTF-8")
246 end
247 request:send_data(document)
248 else
249 error_response("404 Not Found")
250 end
252 end
254 elseif request.method == "POST" then
256 if request.path == "post_example" then
257 local files = {}
258 do
259 local file
260 request:stream_post_param("files", function(chunk, field_name, meta)
261 if meta then
262 file = {
263 file_name = meta.file_name,
264 content_type = meta.content_type,
265 length = 0
266 }
267 end
268 if chunk then
269 file.length = file.length + #chunk
270 else
271 files[#files+1] = file
272 end
273 end)
274 end
276 request:send_status("200 OK")
277 request:send_header("Content-Type", "text/html; chatset=UTF-8")
278 request:send_data("<html>\n<head>\n")
279 request:send_data('<link href="example_webpage.css" rel="stylesheet" type="text/css">\n')
280 request:send_data("<title>Moonbridge Network Server for Lua Applications &ndash; Example Application</title>\n")
281 request:send_data("</head>\n<body>\n")
282 request:send_data("<h1>Moonbridge Network Server for Lua &ndash; Example Application</h1>\n")
283 request:send_data("<h2>POST request successful</h2>\n")
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')
285 for i, file in ipairs(files) do
286 request:send_data("<tr>")
287 request:send_data("<td>", http.encode_html(file.file_name or "(unknown)"), "</td>")
288 request:send_data("<td>", http.encode_html(file.content_type or "(unknown)"), "</td>")
289 request:send_data('<td class="numeric">', http.encode_html(tostring(file.length)), "</td>")
290 request:send_data("</tr>\n")
291 end
292 request:send_data("</tbody>\n</table>\n")
293 request:send_data("<p>Submitted comment: ", http.encode_html(request.post_params.comment), "</p>\n")
294 request:send_data("</body>\n</html>\n")
296 elseif request.path == "chat_send" then
297 local conn = assert(moonbridge_io.tcpconnect("localhost", 8081))
298 local body = request.body
299 local session
300 for line in body:gmatch("[^\r\n]+") do
301 local command, arg = line:match("([^:]+):(.*)")
302 if not command then
303 -- TODO error handling
304 return
305 elseif command == "SESSION" then
306 session = arg
307 else
308 if not session then
309 -- TODO error handling
310 return
311 end
312 conn:write(session .. "\n")
313 if command == "NAME" then
314 local name = arg:gsub(" ", "_")
315 conn:write("NAME:" .. name .. "\n")
316 elseif command == "MSG" then
317 local msg = arg
318 conn:write("MSG:" .. msg .. "\n")
319 end
320 end
321 end
322 conn:flush()
323 conn:close()
324 request:send_status("200 OK")
325 request:send_header("Content-Type", "text/xml; chatset=UTF-8")
327 else
328 error_response("404 Not Found")
330 end
332 else
333 error_response("405 Method not allowed")
335 end
337 -- returning false causes termination of current process (and re-forking)
338 return true
339 end),
341 -- executed on process termination
342 finish = function()
343 -- e.g. close database connection
344 end
345 }

Impressum / About Us