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