moonbridge
annotate example_application.lua @ 124:61a2f55b3538
Do not announce socket/listener type when communicating with the child process
(unnecessary because pointer to listener struct is passed)
(unnecessary because pointer to listener struct is passed)
author | jbe |
---|---|
date | Sun Apr 12 00:33:08 2015 +0200 (2015-04-12) |
parents | 20e0d4f51381 |
children | d9cc81641175 |
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 |
bsw@121 | 157 { proto = "tcp4", port = 8080, localhost = false }, |
jbe@0 | 158 |
jbe@0 | 159 -- listen to a tcp version 6 socket |
bsw@121 | 160 --{ proto = "tcp6", port = 8080, localhost = false }, |
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 – 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 – 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 |