moonbridge

annotate example_application.lua @ 125:d9cc81641175

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

Impressum / About Us