| rev | line source | 
| jbe@203 | 1 #!/usr/bin/env moonbridge | 
| jbe/bsw@0 | 2 | 
| jbe@294 | 3 --[[-- | 
| jbe@294 | 4 WEBMCP_VERSION | 
| jbe@294 | 5 | 
| jbe@294 | 6 A string containing the WebMCP version, e.g. "2.0.0" | 
| jbe@294 | 7 --]]-- | 
| jbe@446 | 8 WEBMCP_VERSION = "2.1.0" | 
| jbe@294 | 9 --//-- | 
| jbe@64 | 10 | 
| jbe@317 | 11 --[[-- | 
| jbe@294 | 12 WEBMCP_MODE | 
| jbe@294 | 13 | 
| jbe@294 | 14 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode. | 
| jbe@294 | 15 --]]-- | 
| jbe@323 | 16 if _MOONBRIDGE_VERSION then | 
| jbe@203 | 17   WEBMCP_MODE = "listen" | 
| jbe@203 | 18 else | 
| jbe@203 | 19   WEBMCP_MODE = "interactive" | 
| jbe@64 | 20 end | 
| jbe@294 | 21 --//-- | 
| jbe@203 | 22 | 
| jbe@294 | 23 --[[-- | 
| jbe@294 | 24 WEBMCP_CONFIG_NAMES | 
| jbe@294 | 25 | 
| jbe@294 | 26 A list of the selected configuration names. | 
| jbe@294 | 27 --]]-- | 
| jbe@294 | 28 -- configuration names are provided as 4th, 5th, etc. command line argument | 
| jbe@206 | 29 WEBMCP_CONFIG_NAMES = {select(4, ...)} | 
| jbe@294 | 30 --//-- | 
| jbe@294 | 31 | 
| jbe@294 | 32 --[[-- | 
| jbe@294 | 33 WEBMCP_FRAMEWORK_PATH | 
| jbe@294 | 34 | 
| jbe@294 | 35 Directory of the WebMCP framework (always includes a trailing slash). | 
| jbe@294 | 36 --]]-- | 
| jbe@294 | 37 -- set in mcp.lua | 
| jbe@294 | 38 --//-- | 
| jbe@294 | 39 | 
| jbe@294 | 40 --[[-- | 
| jbe@294 | 41 WEBMCP_BASE_PATH | 
| jbe@294 | 42 | 
| jbe@294 | 43 Base directory of the application (always includes a trailing slash). | 
| jbe@294 | 44 --]]-- | 
| jbe@294 | 45 -- set in mcp.lua | 
| jbe@294 | 46 --//-- | 
| jbe@294 | 47 | 
| jbe@294 | 48 --[[-- | 
| jbe@294 | 49 WEBMCP_APP_NAME | 
| jbe@294 | 50 | 
| jbe@294 | 51 Application name (usually "main"). May be nil in case of interactive mode. | 
| jbe@294 | 52 --]]-- | 
| jbe@294 | 53 -- set in mcp.lua | 
| jbe@294 | 54 --//-- | 
| jbe@203 | 55 | 
| jbe@203 | 56 -- determine framework and bath path from command line arguments | 
| jbe@203 | 57 -- or print usage synopsis (if applicable) | 
| jbe@68 | 58 do | 
| jbe@206 | 59   local arg1, arg2, arg3 = ... | 
| jbe@203 | 60   local helpout | 
| jbe@203 | 61   if | 
| jbe@203 | 62     arg1 == "-h" or arg1 == "--help" or | 
| jbe@206 | 63     arg2 == "-h" or arg2 == "--help"  -- if first arg is provided by wrapper | 
| jbe@203 | 64   then | 
| jbe@203 | 65     helpout = io.stdout | 
| jbe@316 | 66   elseif #WEBMCP_CONFIG_NAMES < 1 then | 
| jbe@203 | 67     helpout = io.stderr | 
| jbe@203 | 68   end | 
| jbe@203 | 69   if helpout then | 
| jbe@316 | 70     helpout:write("Usage: moonbridge [moonbr opts] -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n") | 
| jbe@316 | 71     helpout:write("   or: lua -i     [Lua opts]    -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n") | 
| jbe@203 | 72     if helpout == io.stderr then | 
| jbe@203 | 73       return 1 | 
| jbe@68 | 74     else | 
| jbe@203 | 75       return 0 | 
| jbe@68 | 76     end | 
| jbe@68 | 77   end | 
| jbe@203 | 78   local function append_trailing_slash(str) | 
| jbe@385 | 79     return (string.gsub(str, "([^/])$", function(last) return last .. "/" end)) | 
| jbe@203 | 80   end | 
| jbe@203 | 81   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1) | 
| jbe@203 | 82   WEBMCP_BASE_PATH      = append_trailing_slash(arg2) | 
| jbe@316 | 83   WEBMCP_APP_NAME       = arg3 | 
| jbe@68 | 84 end | 
| jbe@1 | 85 | 
| jbe@203 | 86 -- setup search paths for libraries | 
| jbe/bsw@0 | 87 do | 
| jbe@217 | 88   if string.match(package.path, "^[^;]") then | 
| jbe@217 | 89     package.path = ";" .. package.path | 
| jbe@217 | 90   end | 
| jbe@217 | 91   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path | 
| jbe/bsw@0 | 92   -- find out which file name extension shared libraries have | 
| jbe/bsw@0 | 93   local slib_exts = {} | 
| jbe/bsw@0 | 94   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do | 
| jbe@217 | 95     if not slib_exts[ext] then | 
| jbe@217 | 96       slib_exts[#slib_exts+1] = ext | 
| jbe@217 | 97       slib_exts[ext] = true | 
| jbe@217 | 98     end | 
| jbe/bsw@0 | 99   end | 
| jbe/bsw@0 | 100   local paths = {} | 
| jbe@217 | 101   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 102     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext | 
| jbe/bsw@0 | 103   end | 
| jbe@217 | 104   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 105     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext | 
| jbe/bsw@0 | 106   end | 
| jbe/bsw@0 | 107   paths[#paths+1] = package.cpath | 
| jbe/bsw@0 | 108   package.cpath = table.concat(paths, ";") | 
| jbe/bsw@0 | 109 end | 
| jbe/bsw@0 | 110 | 
| jbe@352 | 111 -- load "extos" library (needed by function "loadcached") | 
| jbe@352 | 112 _G.extos = require "extos" | 
| jbe@352 | 113 | 
| jbe@352 | 114 --[[-- | 
| jbe@352 | 115 _G | 
| jbe@352 | 116 | 
| jbe@352 | 117 A reference to the global namespace. To avoid accidental programming errors, global variables cannot be set directly, but they must be set through the _G reference, e.g. use _G.foo = true to set the variable "foo" to a value of true. | 
| jbe@352 | 118 | 
| jbe@352 | 119 Note that the global namespace may or may not be shared between requests (Moonbridge creates multiple forks of the Lua machine). To set variables that are to be cleared after the request has been finished, an application may use the "app" table, e.g. app.foo = true to set the variable app.foo to a value of true, which will be cleared automatically when the request has ended. | 
| jbe@352 | 120 | 
| jbe@352 | 121 --]]-- | 
| jbe@352 | 122 local _G = _G | 
| jbe@352 | 123 local allowed_globals = {} | 
| jbe@352 | 124 local protected_environment = setmetatable( | 
| jbe@352 | 125   {},  -- proxy environment used all chunks loaded through loadcached(...) | 
| jbe@352 | 126   { | 
| jbe@352 | 127     __index = _G, | 
| jbe@352 | 128     __newindex = function(self, key, value) | 
| jbe@352 | 129       if allowed_globals[key] then | 
| jbe@352 | 130         _G[key] = value | 
| jbe@352 | 131       else | 
| jbe@352 | 132         if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then | 
| jbe@352 | 133           error('Attempt to set global variable "' .. key .. '" (Hint: missing local statement? Use _G.' .. key .. '=<value> to really set global variable.)', 2) | 
| jbe@352 | 134         else | 
| jbe@352 | 135           error('Attempt to set global variable', 2) | 
| jbe@352 | 136         end | 
| jbe@352 | 137       end | 
| jbe@352 | 138     end | 
| jbe@352 | 139   } | 
| jbe@352 | 140 ) | 
| jbe@352 | 141 --//-- | 
| jbe@352 | 142 | 
| jbe@352 | 143 --[[-- | 
| jbe@354 | 144 lua_func,    -- compiled Lua function, nil if the file does not exist | 
| jbe@354 | 145 errmsg =     -- error message (only for non-existing file, other errors are thrown) | 
| jbe@352 | 146 loadcached( | 
| jbe@352 | 147   filename   -- path to a Lua source or byte-code file | 
| jbe@352 | 148 ) | 
| jbe@352 | 149 | 
| jbe@352 | 150 Loads, compiles and caches a Lua chunk. The cached value (i.e. the compiled function) is returned. If the file does not exist, nil and an error string are returned. Any other errors are thrown using error(...). Unsuccessful attempts are not cached (to prohibit cache pollution). | 
| jbe@352 | 151 | 
| jbe@352 | 152 --]]-- | 
| jbe@352 | 153 do | 
| jbe@352 | 154   local cache = {} | 
| jbe@352 | 155   function loadcached(filename) | 
| jbe@352 | 156     local cached_func = cache[filename] | 
| jbe@352 | 157     if cached_func then | 
| jbe@352 | 158       return cached_func | 
| jbe@352 | 159     end | 
| jbe@352 | 160     local stat, errmsg = extos.stat(filename) | 
| jbe@352 | 161     if stat == nil then | 
| jbe@352 | 162       error(errmsg) | 
| jbe@352 | 163     elseif stat == false then | 
| jbe@352 | 164       return nil, 'File "' .. filename .. '" does not exist' | 
| jbe@352 | 165     elseif stat.isdir then | 
| jbe@352 | 166       error('File "' .. filename .. '" is a directory') | 
| jbe@352 | 167     elseif not stat.isreg then | 
| jbe@352 | 168       error('File "' .. filename .. '" is not a regular file') | 
| jbe@352 | 169     end | 
| jbe@352 | 170     local func, compile_error = loadfile(filename, nil, protected_environment) | 
| jbe@352 | 171     if func then | 
| jbe@352 | 172       cache[filename] = func | 
| jbe@352 | 173       return func | 
| jbe@352 | 174     end | 
| jbe@352 | 175     error(compile_error, 0) | 
| jbe@352 | 176   end | 
| jbe@352 | 177 end | 
| jbe@352 | 178 --//-- | 
| jbe@352 | 179 | 
| jbe@352 | 180 -- check if framework path is correct | 
| jbe@352 | 181 do | 
| jbe@352 | 182   local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r") | 
| jbe@352 | 183   if not file then | 
| jbe@352 | 184     error('Could not find "webmcp_version" file: ' .. errmsg, 0) | 
| jbe@352 | 185   end | 
| jbe@352 | 186   local version = assert(file:read()) | 
| jbe@352 | 187   assert(file:close()) | 
| jbe@352 | 188   if version ~= WEBMCP_VERSION then | 
| jbe@352 | 189     error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"') | 
| jbe@352 | 190   end | 
| jbe@352 | 191 end | 
| jbe@352 | 192 | 
| jbe@203 | 193 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/", | 
| jbe@203 | 194 -- application environment extensions "$WEBMCP_BASE_PATH/env/" | 
| jbe@203 | 195 -- and models "$WEBMCP_BASE_PATH/model/" | 
| jbe/bsw@0 | 196 do | 
| jbe/bsw@0 | 197   local weakkey_mt = { __mode = "k" } | 
| jbe/bsw@0 | 198   local autoloader_category = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 199   local autoloader_path     = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 200   local autoloader_mt       = {} | 
| jbe@219 | 201   local function install_autoloader(self, category, path_fragment) | 
| jbe/bsw@0 | 202     autoloader_category[self] = category | 
| jbe@219 | 203     autoloader_path[self]     = path_fragment | 
| jbe/bsw@0 | 204     setmetatable(self, autoloader_mt) | 
| jbe/bsw@0 | 205   end | 
| jbe/bsw@0 | 206   local function try_exec(filename) | 
| jbe@309 | 207     local func = loadcached(filename) | 
| jbe@309 | 208     if func then | 
| jbe@309 | 209       func() | 
| jbe@309 | 210       return true | 
| jbe/bsw@0 | 211     else | 
| jbe/bsw@0 | 212       return false | 
| jbe/bsw@0 | 213     end | 
| jbe/bsw@0 | 214   end | 
| jbe/bsw@0 | 215   function autoloader_mt.__index(self, key) | 
| jbe/bsw@0 | 216     local category, base_path, merge_base_path, file_key | 
| jbe/bsw@0 | 217     local merge = false | 
| jbe/bsw@0 | 218     if | 
| jbe/bsw@0 | 219       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and | 
| jbe/bsw@0 | 220       not string.find(key, "^__") | 
| jbe/bsw@0 | 221     then | 
| jbe/bsw@0 | 222       category        = "env" | 
| jbe@203 | 223       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/" | 
| jbe/bsw@0 | 224       merge           = true | 
| jbe@203 | 225       merge_base_path = WEBMCP_BASE_PATH .. "env/" | 
| jbe/bsw@0 | 226       file_key        = key | 
| jbe/bsw@0 | 227     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then | 
| jbe/bsw@0 | 228       category        = "model" | 
| jbe@203 | 229       base_path       = WEBMCP_BASE_PATH .. "model/" | 
| jbe/bsw@0 | 230       local first = true | 
| jbe/bsw@0 | 231       file_key = string.gsub(key, "[A-Z]", | 
| jbe/bsw@0 | 232         function(c) | 
| jbe/bsw@0 | 233           if first then | 
| jbe/bsw@0 | 234             first = false | 
| jbe/bsw@0 | 235             return string.lower(c) | 
| jbe/bsw@0 | 236           else | 
| jbe/bsw@0 | 237             return "_" .. string.lower(c) | 
| jbe/bsw@0 | 238           end | 
| jbe/bsw@0 | 239         end | 
| jbe/bsw@0 | 240       ) | 
| jbe/bsw@0 | 241     else | 
| jbe/bsw@0 | 242       return | 
| jbe/bsw@0 | 243     end | 
| jbe/bsw@0 | 244     local required_category = autoloader_category[self] | 
| jbe/bsw@0 | 245     if required_category and required_category ~= category then return end | 
| jbe@219 | 246     local path_fragment = autoloader_path[self] | 
| jbe@219 | 247     local path = base_path .. path_fragment .. file_key | 
| jbe@219 | 248     local merge_path | 
| jbe/bsw@0 | 249     if merge then | 
| jbe@219 | 250       merge_path = merge_base_path .. path_fragment .. file_key | 
| jbe/bsw@0 | 251     end | 
| jbe/bsw@0 | 252     local function try_dir(dirname) | 
| jbe/bsw@0 | 253       local dir = io.open(dirname) | 
| jbe/bsw@0 | 254       if dir then | 
| jbe/bsw@0 | 255         io.close(dir) | 
| jbe/bsw@0 | 256         local obj = {} | 
| jbe@219 | 257         install_autoloader(obj, category, path_fragment .. file_key .. "/") | 
| jbe/bsw@0 | 258         rawset(self, key, obj) | 
| jbe@219 | 259         try_exec(path .. "/__init.lua") | 
| jbe@219 | 260         if merge then try_exec(merge_path .. "/__init.lua") end | 
| jbe/bsw@0 | 261         return true | 
| jbe/bsw@0 | 262       else | 
| jbe/bsw@0 | 263         return false | 
| jbe/bsw@0 | 264       end | 
| jbe/bsw@0 | 265     end | 
| jbe@238 | 266     if self == _G then | 
| jbe@237 | 267       allowed_globals[key] = true | 
| jbe@233 | 268     end | 
| jbe@219 | 269     if merge and try_exec(merge_path .. ".lua") then | 
| jbe@219 | 270     elseif merge and try_dir(merge_path .. "/") then | 
| jbe@219 | 271     elseif try_exec(path .. ".lua") then | 
| jbe@219 | 272     elseif try_dir(path .. "/") then | 
| jbe/bsw@0 | 273     else end | 
| jbe@238 | 274     if self == _G then | 
| jbe@237 | 275       allowed_globals[key] = nil | 
| jbe@233 | 276     end | 
| jbe@237 | 277     return rawget(self, key) | 
| jbe/bsw@0 | 278   end | 
| jbe@219 | 279   install_autoloader(_G, nil, "") | 
| jbe@324 | 280   try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua") | 
| jbe@324 | 281   try_exec(WEBMCP_BASE_PATH .. "env/__init.lua") | 
| jbe@214 | 282 end | 
| jbe@214 | 283 | 
| jbe@480 | 284 -- signal socket (to catch SIGTERM in main handlers) | 
| jbe@480 | 285 local sigterm_socket | 
| jbe@480 | 286 | 
| jbe@286 | 287 -- define post-fork initialization function (including loading of "multirand" library) | 
| jbe@286 | 288 local function postfork_init() | 
| jbe@324 | 289   multirand = require "multirand" | 
| jbe@286 | 290   execute.postfork_initializers() | 
| jbe@286 | 291 end | 
| jbe@286 | 292 | 
| jbe@336 | 293 --[[-- | 
| jbe@336 | 294 listen{ | 
| jbe@336 | 295   { | 
| jbe@474 | 296     proto     = proto,    -- "local", "tcp", "interval", or "main" | 
| jbe@474 | 297     path      = path,     -- path to unix domain socket if proto == "local" | 
| jbe@474 | 298     port      = port,     -- TCP port number | 
| jbe@474 | 299     host      = host,     -- "::" for all IPv6 interfaces, "0.0.0.0" for all IPv4 interfaces | 
| jbe@474 | 300     name      = name,     -- optional name for main handlers or interval handlers (may be useful for log output) | 
| jbe@474 | 301     handler   = handler,  -- handler if proto == "interval" or proto == "main" | 
| jbe@474 | 302     delay     = delay,    -- delay between invocations of interval handler | 
| jbe@474 | 303     strict    = strict    -- set to true to substract runtime of interval handler from delay | 
| jbe@336 | 304   }, | 
| jbe@336 | 305   { | 
| jbe@336 | 306     ...                           -- second listener | 
| jbe@336 | 307   }, | 
| jbe@336 | 308   ...                             -- more listeners | 
| jbe@336 | 309   -- the following options are all optional and have default values: | 
| jbe@336 | 310   pre_fork         = pre_fork,          -- desired number of spare (idle) processes | 
| jbe@336 | 311   min_fork         = min_fork,          -- minimum number of processes | 
| jbe@336 | 312   max_fork         = max_fork,          -- maximum number of processes (hard limit) | 
| jbe@336 | 313   fork_delay       = fork_delay,        -- delay (seconds) between creation of spare processes | 
| jbe@336 | 314   fork_error_delay = fork_error_delay,  -- delay (seconds) before retry of failed process creation | 
| jbe@336 | 315   exit_delay       = exit_delay,        -- delay (seconds) between destruction of excessive spare processes | 
| jbe@336 | 316   idle_timeout     = idle_timeout,      -- idle time (seconds) after a fork gets terminated (0 for no timeout) | 
| jbe@336 | 317   memory_limit     = memory_limit,      -- maximum memory consumption (bytes) before process gets terminated | 
| jbe@336 | 318   min_requests_per_fork = min_requests_per_fork,  -- minimum count of requests handled before fork is terminated | 
| jbe@336 | 319   max_requests_per_fork = max_requests_per_fork,  -- maximum count of requests handled before fork is terminated | 
| jbe@336 | 320   http_options = { | 
| jbe@337 | 321     static_headers            = static_headers,             -- string or table of static headers to be returned with every request | 
| jbe@337 | 322     request_header_size_limit = request_header_size_limit,  -- maximum size of request headers sent by client | 
| jbe@337 | 323     request_body_size_limit   = request_body_size_limit,    -- maximum size of request body sent by client | 
| jbe@367 | 324     idle_timeout              = idle_timeout,               -- maximum time until receiving the first byte of the request header | 
| jbe@367 | 325     stall_timeout             = stall_timeout,              -- maximum time a client connection may be stalled | 
| jbe@366 | 326     request_header_timeout    = request_header_timeout,     -- maximum time until receiving the remaining bytes of the request header | 
| jbe@366 | 327     response_timeout          = response_timeout,           -- time in which request body and response must be sent | 
| jbe@338 | 328     maximum_input_chunk_size  = maximum_input_chunk_size,   -- tweaks behavior of request-body parser | 
| jbe@337 | 329     minimum_output_chunk_size = minimum_output_chunk_size   -- chunk size for chunked-transfer-encoding | 
| jbe@336 | 330   } | 
| jbe@336 | 331 } | 
| jbe@336 | 332 | 
| jbe@336 | 333 The listen{...} function determines on which TCP port an application is answering requests. A typical call looks as follows: | 
| jbe@336 | 334 | 
| jbe@336 | 335 listen{ | 
| jbe@336 | 336   { proto = "tcp4", port = 8080, localhost = true }, | 
| jbe@336 | 337   { proto = "tcp6", port = 8080, localhost = true } | 
| jbe@336 | 338 } | 
| jbe@336 | 339 | 
| jbe@336 | 340 This function must be called in a configuration file (in the config/ directory) or in pre-fork initializers (in the app/_prefork/ or app/<application name>/_prefork/ directories), unless WebMCP is invoked in interactive mode (in which case any calls of listen{...} are ignored). | 
| jbe@336 | 341 | 
| jbe@483 | 342 This function is a variant of Moonbridge's listen{...} function which has been wrapped for WebMCP. No "prepare", "conenct", or "finish" handler can be set. Instead WebMCP automatically dispatches incoming connections. For interval timers and main routines, a handler may be specified in each listener. If a main handler returns, the WebMCP system will shut down. | 
| jbe@480 | 343 | 
| jbe@336 | 344 --]]-- | 
| jbe@317 | 345 -- prepare for interactive or listen mode | 
| jbe@203 | 346 if WEBMCP_MODE == "interactive" then | 
| jbe@317 | 347   function listen()  -- overwrite Moonbridge's listen function | 
| jbe@317 | 348     -- ignore listen function calls for interactive mode | 
| jbe@317 | 349   end | 
| jbe@317 | 350   trace.disable()  -- avoids memory leakage when scripts are running endlessly | 
| jbe@317 | 351 else | 
| jbe@317 | 352   local moonbridge_listen = listen | 
| jbe@207 | 353   local http = require("moonbridge_http") | 
| jbe@317 | 354   function listen(args)  -- overwrite Moonbridge's listen function | 
| jbe@322 | 355     assert(args, "No argument passed to listen function") | 
| jbe@322 | 356     local min_requests_per_fork = args.min_requests_per_fork or 50 | 
| jbe@335 | 357     local max_requests_per_fork = args.max_requests_per_fork or 200 | 
| jbe@473 | 358     local main_handlers = {} | 
| jbe@316 | 359     local interval_handlers = {} | 
| jbe@317 | 360     for j, listener in ipairs(args) do | 
| jbe@473 | 361       if listener.proto == "main" then | 
| jbe@473 | 362         local name = listener.name or "Unnamed main thread #" .. #main_handlers+1 | 
| jbe@473 | 363         if main_handlers[name] ~= nil then | 
| jbe@473 | 364           error('Main thread handler with duplicate name "' .. name .. '"') | 
| jbe@473 | 365         end | 
| jbe@473 | 366         main_handlers[name] = listener.handler | 
| jbe@473 | 367         listener.name = name | 
| jbe@473 | 368       elseif listener.proto == "interval" then | 
| jbe@317 | 369         local name = listener.name or "Unnamed interval #" .. #interval_handlers+1 | 
| jbe@336 | 370         if interval_handlers[name] ~= nil then | 
| jbe@336 | 371           error('Interval handler with duplicate name "' .. name .. '"') | 
| jbe@336 | 372         end | 
| jbe@317 | 373         interval_handlers[name] = listener.handler | 
| jbe@317 | 374         listener.name = name | 
| jbe@316 | 375       end | 
| jbe@316 | 376     end | 
| jbe@264 | 377     local request_count = 0 | 
| jbe@266 | 378     local function inner_handler(http_request) | 
| jbe@328 | 379       request_count = request_count + 1 | 
| jbe@328 | 380       if request_count >= max_requests_per_fork then | 
| jbe@328 | 381         http_request:close_after_finish() | 
| jbe@328 | 382       end | 
| jbe@317 | 383       request.initialize() | 
| jbe@328 | 384       return request.handler(http_request) | 
| jbe@264 | 385     end | 
| jbe@326 | 386     local outer_handler = http.generate_handler(inner_handler, args.http_options) | 
| jbe@317 | 387     args.prepare = postfork_init | 
| jbe@317 | 388     args.connect = function(socket) | 
| jbe@473 | 389       if socket.main then | 
| jbe@473 | 390         request.initialize() | 
| jbe@483 | 391         main_handlers[socket.main]() | 
| jbe@479 | 392         io.stderr:write('Main handler "' .. socket.main .. '" terminated. Requesting shutdown.\n') | 
| jbe@473 | 393         return false | 
| jbe@473 | 394       elseif socket.interval then | 
| jbe@328 | 395         request_count = request_count + 1 | 
| jbe@317 | 396         request.initialize() | 
| jbe@316 | 397         interval_handlers[socket.interval]() | 
| jbe@316 | 398       else | 
| jbe@327 | 399         local success = outer_handler(socket) | 
| jbe@327 | 400         if not success then | 
| jbe@327 | 401           return false | 
| jbe@327 | 402         end | 
| jbe@316 | 403       end | 
| jbe@288 | 404       return request_count < min_requests_per_fork | 
| jbe@264 | 405     end | 
| jbe@317 | 406     args.finish = execute.finalizers | 
| jbe@317 | 407     moonbridge_listen(args) | 
| jbe@204 | 408   end | 
| jbe@204 | 409 end | 
| jbe@336 | 410 --//-- | 
| jbe@317 | 411 | 
| jbe@317 | 412 -- execute configurations and pre-fork initializers | 
| jbe@317 | 413 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do | 
| jbe@317 | 414   execute.config(config_name) | 
| jbe@317 | 415 end | 
| jbe@317 | 416 execute.prefork_initializers() | 
| jbe@317 | 417 | 
| jbe@324 | 418 -- perform post-fork initializations (once) in case of interactive mode | 
| jbe@317 | 419 if WEBMCP_MODE == "interactive" then | 
| jbe@317 | 420   postfork_init() | 
| jbe@317 | 421 end |