moonbridge

annotate example_application.lua @ 158:8ab33bfb47e7

Minimal changes to HTTP module to support interface of new :read(...) method
author jbe
date Wed May 27 02:55:48 2015 +0200 (2015-05-27)
parents 89fc89b22e28
children 5adfc36ca73f
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 &ndash; 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 &ndash; 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

Impressum / About Us