| 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@278 | 8 WEBMCP_VERSION = "2.0.0" | 
| jbe@294 | 9 --//-- | 
| jbe@64 | 10 | 
| jbe@231 | 11 -- allow control of global environment | 
| jbe@231 | 12 local _G = _G | 
| jbe@237 | 13 local allowed_globals = {} | 
| jbe@231 | 14 local global_metatable = { | 
| jbe@231 | 15   __index = _G, | 
| jbe@231 | 16   __newindex = function(self, key, value) | 
| jbe@231 | 17     _G[key] = value | 
| jbe@231 | 18   end | 
| jbe@231 | 19 } | 
| jbe@231 | 20 _ENV = setmetatable({}, global_metatable) | 
| jbe@231 | 21 | 
| jbe@294 | 22 --[[-- | 
| jbe@309 | 23 lua_func =   -- compiled Lua function | 
| jbe@309 | 24 loadcached( | 
| jbe@309 | 25   filename   -- path to a Lua source or byte-code file | 
| jbe@309 | 26 ) | 
| jbe@309 | 27 | 
| jbe@309 | 28 Loads, compiles and caches a Lua chunk. If the file does not exist, nil and an error string are returned. Otherwise the file is loaded, compiled, and cached. The cached value (i.e. the compiled function) is returned. An error is raised if the compilation was not successful. | 
| jbe@309 | 29 | 
| jbe@309 | 30 --]]-- | 
| jbe@309 | 31 do | 
| jbe@309 | 32   local cache = {} | 
| jbe@309 | 33   function loadcached(filename) | 
| jbe@311 | 34     local cached_func = cache[filename] | 
| jbe@311 | 35     if cached_func then | 
| jbe@311 | 36       return cached_func | 
| jbe@311 | 37     end | 
| jbe@309 | 38     local file, read_error = io.open(filename, "r") | 
| jbe@309 | 39     if file then | 
| jbe@309 | 40       local filedata = assert(file:read("*a")) | 
| jbe@309 | 41       assert(file:close()) | 
| jbe@309 | 42       local func, compile_error = load(filedata, "=" .. filename, nil, _ENV) | 
| jbe@309 | 43       if func then | 
| jbe@309 | 44         cache[filename] = func | 
| jbe@309 | 45         return func | 
| jbe@309 | 46       end | 
| jbe@311 | 47       error(compile_error, 0) | 
| jbe@309 | 48     else | 
| jbe@309 | 49       return nil, read_error | 
| jbe@309 | 50     end | 
| jbe@309 | 51   end | 
| jbe@309 | 52 end | 
| jbe@309 | 53 --//-- | 
| jbe@309 | 54 | 
| jbe@309 | 55 --[[-- | 
| jbe@294 | 56 WEBMCP_MODE | 
| jbe@294 | 57 | 
| jbe@294 | 58 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode. | 
| jbe@294 | 59 --]]-- | 
| jbe@203 | 60 -- check if interactive mode | 
| jbe@203 | 61 if listen then  -- defined by moonbridge | 
| jbe@203 | 62   WEBMCP_MODE = "listen" | 
| jbe@203 | 63 else | 
| jbe@203 | 64   WEBMCP_MODE = "interactive" | 
| jbe@64 | 65 end | 
| jbe@294 | 66 --//-- | 
| jbe@203 | 67 | 
| jbe@294 | 68 --[[-- | 
| jbe@294 | 69 WEBMCP_CONFIG_NAMES | 
| jbe@294 | 70 | 
| jbe@294 | 71 A list of the selected configuration names. | 
| jbe@294 | 72 --]]-- | 
| jbe@294 | 73 -- configuration names are provided as 4th, 5th, etc. command line argument | 
| jbe@206 | 74 WEBMCP_CONFIG_NAMES = {select(4, ...)} | 
| jbe@294 | 75 --//-- | 
| jbe@294 | 76 | 
| jbe@294 | 77 --[[-- | 
| jbe@294 | 78 WEBMCP_FRAMEWORK_PATH | 
| jbe@294 | 79 | 
| jbe@294 | 80 Directory of the WebMCP framework (always includes a trailing slash). | 
| jbe@294 | 81 --]]-- | 
| jbe@294 | 82 -- set in mcp.lua | 
| jbe@294 | 83 --//-- | 
| jbe@294 | 84 | 
| jbe@294 | 85 --[[-- | 
| jbe@294 | 86 WEBMCP_BASE_PATH | 
| jbe@294 | 87 | 
| jbe@294 | 88 Base directory of the application (always includes a trailing slash). | 
| jbe@294 | 89 --]]-- | 
| jbe@294 | 90 -- set in mcp.lua | 
| jbe@294 | 91 --//-- | 
| jbe@294 | 92 | 
| jbe@294 | 93 --[[-- | 
| jbe@294 | 94 WEBMCP_APP_NAME | 
| jbe@294 | 95 | 
| jbe@294 | 96 Application name (usually "main"). May be nil in case of interactive mode. | 
| jbe@294 | 97 --]]-- | 
| jbe@294 | 98 -- set in mcp.lua | 
| jbe@294 | 99 --//-- | 
| jbe@203 | 100 | 
| jbe@203 | 101 -- determine framework and bath path from command line arguments | 
| jbe@203 | 102 -- or print usage synopsis (if applicable) | 
| jbe@68 | 103 do | 
| jbe@206 | 104   local arg1, arg2, arg3 = ... | 
| jbe@203 | 105   local helpout | 
| jbe@203 | 106   if | 
| jbe@203 | 107     arg1 == "-h" or arg1 == "--help" or | 
| jbe@206 | 108     arg2 == "-h" or arg2 == "--help"  -- if first arg is provided by wrapper | 
| jbe@203 | 109   then | 
| jbe@203 | 110     helpout = io.stdout | 
| jbe@316 | 111   elseif #WEBMCP_CONFIG_NAMES < 1 then | 
| jbe@203 | 112     helpout = io.stderr | 
| jbe@203 | 113   end | 
| jbe@203 | 114   if helpout then | 
| jbe@316 | 115     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 | 116     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 | 117     if helpout == io.stderr then | 
| jbe@203 | 118       return 1 | 
| jbe@68 | 119     else | 
| jbe@203 | 120       return 0 | 
| jbe@68 | 121     end | 
| jbe@68 | 122   end | 
| jbe@203 | 123   local function append_trailing_slash(str) | 
| jbe@217 | 124     return string.gsub(str, "([^/])$", function(last) return last .. "/" end) | 
| jbe@203 | 125   end | 
| jbe@203 | 126   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1) | 
| jbe@203 | 127   WEBMCP_BASE_PATH      = append_trailing_slash(arg2) | 
| jbe@316 | 128   WEBMCP_APP_NAME       = arg3 | 
| jbe@68 | 129 end | 
| jbe@1 | 130 | 
| jbe@313 | 131 -- check if framework path is correct | 
| jbe@313 | 132 do | 
| jbe@313 | 133   local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r") | 
| jbe@313 | 134   if not file then | 
| jbe@313 | 135     error('Could not find "webmcp_version" file: ' .. errmsg, 0) | 
| jbe@313 | 136   end | 
| jbe@313 | 137   local version = assert(file:read()) | 
| jbe@313 | 138   assert(file:close()) | 
| jbe@313 | 139   if version ~= WEBMCP_VERSION then | 
| jbe@313 | 140     error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"') | 
| jbe@313 | 141   end | 
| jbe@313 | 142 end | 
| jbe@313 | 143 | 
| jbe@203 | 144 -- setup search paths for libraries | 
| jbe/bsw@0 | 145 do | 
| jbe@217 | 146   if string.match(package.path, "^[^;]") then | 
| jbe@217 | 147     package.path = ";" .. package.path | 
| jbe@217 | 148   end | 
| jbe@217 | 149   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path | 
| jbe/bsw@0 | 150   -- find out which file name extension shared libraries have | 
| jbe/bsw@0 | 151   local slib_exts = {} | 
| jbe/bsw@0 | 152   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do | 
| jbe@217 | 153     if not slib_exts[ext] then | 
| jbe@217 | 154       slib_exts[#slib_exts+1] = ext | 
| jbe@217 | 155       slib_exts[ext] = true | 
| jbe@217 | 156     end | 
| jbe/bsw@0 | 157   end | 
| jbe/bsw@0 | 158   local paths = {} | 
| jbe@217 | 159   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 160     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext | 
| jbe/bsw@0 | 161   end | 
| jbe@217 | 162   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 163     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext | 
| jbe/bsw@0 | 164   end | 
| jbe/bsw@0 | 165   paths[#paths+1] = package.cpath | 
| jbe/bsw@0 | 166   package.cpath = table.concat(paths, ";") | 
| jbe/bsw@0 | 167 end | 
| jbe/bsw@0 | 168 | 
| jbe@203 | 169 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/", | 
| jbe@203 | 170 -- application environment extensions "$WEBMCP_BASE_PATH/env/" | 
| jbe@203 | 171 -- and models "$WEBMCP_BASE_PATH/model/" | 
| jbe/bsw@0 | 172 do | 
| jbe/bsw@0 | 173   local weakkey_mt = { __mode = "k" } | 
| jbe/bsw@0 | 174   local autoloader_category = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 175   local autoloader_path     = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 176   local autoloader_mt       = {} | 
| jbe@219 | 177   local function install_autoloader(self, category, path_fragment) | 
| jbe/bsw@0 | 178     autoloader_category[self] = category | 
| jbe@219 | 179     autoloader_path[self]     = path_fragment | 
| jbe/bsw@0 | 180     setmetatable(self, autoloader_mt) | 
| jbe/bsw@0 | 181   end | 
| jbe/bsw@0 | 182   local function try_exec(filename) | 
| jbe@309 | 183     local func = loadcached(filename) | 
| jbe@309 | 184     if func then | 
| jbe@309 | 185       func() | 
| jbe@309 | 186       return true | 
| jbe/bsw@0 | 187     else | 
| jbe/bsw@0 | 188       return false | 
| jbe/bsw@0 | 189     end | 
| jbe/bsw@0 | 190   end | 
| jbe/bsw@0 | 191   function autoloader_mt.__index(self, key) | 
| jbe/bsw@0 | 192     local category, base_path, merge_base_path, file_key | 
| jbe/bsw@0 | 193     local merge = false | 
| jbe/bsw@0 | 194     if | 
| jbe/bsw@0 | 195       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and | 
| jbe/bsw@0 | 196       not string.find(key, "^__") | 
| jbe/bsw@0 | 197     then | 
| jbe/bsw@0 | 198       category        = "env" | 
| jbe@203 | 199       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/" | 
| jbe/bsw@0 | 200       merge           = true | 
| jbe@203 | 201       merge_base_path = WEBMCP_BASE_PATH .. "env/" | 
| jbe/bsw@0 | 202       file_key        = key | 
| jbe/bsw@0 | 203     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then | 
| jbe/bsw@0 | 204       category        = "model" | 
| jbe@203 | 205       base_path       = WEBMCP_BASE_PATH .. "model/" | 
| jbe/bsw@0 | 206       local first = true | 
| jbe/bsw@0 | 207       file_key = string.gsub(key, "[A-Z]", | 
| jbe/bsw@0 | 208         function(c) | 
| jbe/bsw@0 | 209           if first then | 
| jbe/bsw@0 | 210             first = false | 
| jbe/bsw@0 | 211             return string.lower(c) | 
| jbe/bsw@0 | 212           else | 
| jbe/bsw@0 | 213             return "_" .. string.lower(c) | 
| jbe/bsw@0 | 214           end | 
| jbe/bsw@0 | 215         end | 
| jbe/bsw@0 | 216       ) | 
| jbe/bsw@0 | 217     else | 
| jbe/bsw@0 | 218       return | 
| jbe/bsw@0 | 219     end | 
| jbe/bsw@0 | 220     local required_category = autoloader_category[self] | 
| jbe/bsw@0 | 221     if required_category and required_category ~= category then return end | 
| jbe@219 | 222     local path_fragment = autoloader_path[self] | 
| jbe@219 | 223     local path = base_path .. path_fragment .. file_key | 
| jbe@219 | 224     local merge_path | 
| jbe/bsw@0 | 225     if merge then | 
| jbe@219 | 226       merge_path = merge_base_path .. path_fragment .. file_key | 
| jbe/bsw@0 | 227     end | 
| jbe/bsw@0 | 228     local function try_dir(dirname) | 
| jbe/bsw@0 | 229       local dir = io.open(dirname) | 
| jbe/bsw@0 | 230       if dir then | 
| jbe/bsw@0 | 231         io.close(dir) | 
| jbe/bsw@0 | 232         local obj = {} | 
| jbe@219 | 233         install_autoloader(obj, category, path_fragment .. file_key .. "/") | 
| jbe/bsw@0 | 234         rawset(self, key, obj) | 
| jbe@219 | 235         try_exec(path .. "/__init.lua") | 
| jbe@219 | 236         if merge then try_exec(merge_path .. "/__init.lua") end | 
| jbe/bsw@0 | 237         return true | 
| jbe/bsw@0 | 238       else | 
| jbe/bsw@0 | 239         return false | 
| jbe/bsw@0 | 240       end | 
| jbe/bsw@0 | 241     end | 
| jbe@238 | 242     if self == _G then | 
| jbe@237 | 243       allowed_globals[key] = true | 
| jbe@233 | 244     end | 
| jbe@219 | 245     if merge and try_exec(merge_path .. ".lua") then | 
| jbe@219 | 246     elseif merge and try_dir(merge_path .. "/") then | 
| jbe@219 | 247     elseif try_exec(path .. ".lua") then | 
| jbe@219 | 248     elseif try_dir(path .. "/") then | 
| jbe/bsw@0 | 249     else end | 
| jbe@238 | 250     if self == _G then | 
| jbe@237 | 251       allowed_globals[key] = nil | 
| jbe@233 | 252     end | 
| jbe@237 | 253     return rawget(self, key) | 
| jbe/bsw@0 | 254   end | 
| jbe@219 | 255   install_autoloader(_G, nil, "") | 
| jbe@203 | 256   try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua") | 
| jbe@203 | 257   try_exec(WEBMCP_BASE_PATH .. "env/__init.lua") | 
| jbe/bsw@0 | 258 end | 
| jbe/bsw@0 | 259 | 
| jbe@214 | 260 -- replace Moonbridge listen function | 
| jbe@214 | 261 local moonbridge_listen = listen | 
| jbe@225 | 262 local listeners = {} | 
| jbe@214 | 263 function listen(args) | 
| jbe@214 | 264   listeners[#listeners+1] = args | 
| jbe@214 | 265 end | 
| jbe@214 | 266 | 
| jbe@203 | 267 -- prohibit (unintended) definition of new global variables | 
| jbe@237 | 268 function global_metatable.__newindex(self, key, value) | 
| jbe@237 | 269   if not allowed_globals[key] then | 
| jbe@237 | 270     error("Setting of global variable prohibited", 2) | 
| jbe@237 | 271   end | 
| jbe@239 | 272   _G[key] = value | 
| jbe@231 | 273 end | 
| jbe@203 | 274 | 
| jbe@220 | 275 -- execute configurations and pre-fork initializers | 
| jbe@206 | 276 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do | 
| jbe@206 | 277   execute.config(config_name) | 
| jbe@206 | 278 end | 
| jbe@220 | 279 execute.prefork_initializers() | 
| jbe@206 | 280 | 
| jbe@286 | 281 -- define post-fork initialization function (including loading of "multirand" library) | 
| jbe@286 | 282 local function postfork_init() | 
| jbe@286 | 283   _G.multirand = require "multirand" | 
| jbe@286 | 284   execute.postfork_initializers() | 
| jbe@286 | 285 end | 
| jbe@286 | 286 | 
| jbe/bsw@0 | 287 -- interactive console mode | 
| jbe@203 | 288 if WEBMCP_MODE == "interactive" then | 
| jbe@286 | 289   postfork_init() | 
| jbe@289 | 290   trace.disable()  -- avoids memory leakage | 
| jbe/bsw@0 | 291 end | 
| jbe/bsw@0 | 292 | 
| jbe@204 | 293 -- invoke moonbridge | 
| jbe@206 | 294 if WEBMCP_MODE == "listen" then | 
| jbe@264 | 295   local http_options = request.get_http_options() | 
| jbe@288 | 296   local min_requests_per_fork  = http_options.min_requests_per_fork or 50 | 
| jbe@288 | 297   local max_requests_per_fork  = http_options.max_requests_per_fork or 100 | 
| jbe@207 | 298   local http = require("moonbridge_http") | 
| jbe@211 | 299   for i, listener in ipairs(listeners) do | 
| jbe@316 | 300     local interval_handlers = {} | 
| jbe@316 | 301     for j, entry in ipairs(listener) do | 
| jbe@316 | 302       if entry.proto == "interval" then | 
| jbe@316 | 303         local name = entry.name or "Unnamed interval #" .. #interval_handlers+1 | 
| jbe@316 | 304         interval_handlers[name] = entry.handler | 
| jbe@316 | 305         entry.name = name | 
| jbe@316 | 306       end | 
| jbe@316 | 307     end | 
| jbe@264 | 308     local request_count = 0 | 
| jbe@266 | 309     local function inner_handler(http_request) | 
| jbe@288 | 310       request.handler(http_request, request_count >= max_requests_per_fork) | 
| jbe@264 | 311     end | 
| jbe@264 | 312     local outer_handler = http.generate_handler(inner_handler, http_options) | 
| jbe@286 | 313     listener.prepare = postfork_init | 
| jbe@264 | 314     listener.connect = function(socket) | 
| jbe@316 | 315       request_count = request_count + 1 | 
| jbe@316 | 316       request.initialize() | 
| jbe@316 | 317       if socket.interval then | 
| jbe@316 | 318         interval_handlers[socket.interval]() | 
| jbe@316 | 319       else | 
| jbe@316 | 320         outer_handler(socket) | 
| jbe@316 | 321       end | 
| jbe@288 | 322       return request_count < min_requests_per_fork | 
| jbe@264 | 323     end | 
| jbe@211 | 324     listener.finish = execute.finalizers | 
| jbe@211 | 325     moonbridge_listen(listener) | 
| jbe@204 | 326   end | 
| jbe@204 | 327 end |