| 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@203 | 111   elseif | 
| jbe@217 | 112     #WEBMCP_CONFIG_NAMES < 1 or | 
| jbe@206 | 113     (WEBMCP_MODE == "interactive") ~= (arg3 == "INTERACTIVE") | 
| jbe@203 | 114   then | 
| jbe@203 | 115     helpout = io.stderr | 
| jbe@203 | 116   end | 
| jbe@203 | 117   if helpout then | 
| jbe@217 | 118     helpout:write("Usage: moonbridge -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n") | 
| jbe@217 | 119     helpout:write("   or: lua -i <framework path>/bin/mcp.lua <framework path> <app base path> INTERACTIVE <config name> [<config name> ...]\n") | 
| jbe@203 | 120     if helpout == io.stderr then | 
| jbe@203 | 121       return 1 | 
| jbe@68 | 122     else | 
| jbe@203 | 123       return 0 | 
| jbe@68 | 124     end | 
| jbe@68 | 125   end | 
| jbe@203 | 126   local function append_trailing_slash(str) | 
| jbe@217 | 127     return string.gsub(str, "([^/])$", function(last) return last .. "/" end) | 
| jbe@203 | 128   end | 
| jbe@203 | 129   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1) | 
| jbe@203 | 130   WEBMCP_BASE_PATH      = append_trailing_slash(arg2) | 
| jbe@206 | 131   if WEBMCP_MODE == "listen" then | 
| jbe@206 | 132     WEBMCP_APP_NAME = arg3 | 
| jbe@206 | 133   end | 
| jbe@68 | 134 end | 
| jbe@1 | 135 | 
| jbe@203 | 136 -- setup search paths for libraries | 
| jbe/bsw@0 | 137 do | 
| jbe@217 | 138   if string.match(package.path, "^[^;]") then | 
| jbe@217 | 139     package.path = ";" .. package.path | 
| jbe@217 | 140   end | 
| jbe@217 | 141   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path | 
| jbe/bsw@0 | 142   -- find out which file name extension shared libraries have | 
| jbe/bsw@0 | 143   local slib_exts = {} | 
| jbe/bsw@0 | 144   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do | 
| jbe@217 | 145     if not slib_exts[ext] then | 
| jbe@217 | 146       slib_exts[#slib_exts+1] = ext | 
| jbe@217 | 147       slib_exts[ext] = true | 
| jbe@217 | 148     end | 
| jbe/bsw@0 | 149   end | 
| jbe/bsw@0 | 150   local paths = {} | 
| jbe@217 | 151   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 152     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext | 
| jbe/bsw@0 | 153   end | 
| jbe@217 | 154   for i, ext in ipairs(slib_exts) do | 
| jbe@203 | 155     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext | 
| jbe/bsw@0 | 156   end | 
| jbe/bsw@0 | 157   paths[#paths+1] = package.cpath | 
| jbe/bsw@0 | 158   package.cpath = table.concat(paths, ";") | 
| jbe/bsw@0 | 159 end | 
| jbe/bsw@0 | 160 | 
| jbe@203 | 161 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/", | 
| jbe@203 | 162 -- application environment extensions "$WEBMCP_BASE_PATH/env/" | 
| jbe@203 | 163 -- and models "$WEBMCP_BASE_PATH/model/" | 
| jbe/bsw@0 | 164 do | 
| jbe/bsw@0 | 165   local weakkey_mt = { __mode = "k" } | 
| jbe/bsw@0 | 166   local autoloader_category = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 167   local autoloader_path     = setmetatable({}, weakkey_mt) | 
| jbe/bsw@0 | 168   local autoloader_mt       = {} | 
| jbe@219 | 169   local function install_autoloader(self, category, path_fragment) | 
| jbe/bsw@0 | 170     autoloader_category[self] = category | 
| jbe@219 | 171     autoloader_path[self]     = path_fragment | 
| jbe/bsw@0 | 172     setmetatable(self, autoloader_mt) | 
| jbe/bsw@0 | 173   end | 
| jbe/bsw@0 | 174   local function try_exec(filename) | 
| jbe@309 | 175     local func = loadcached(filename) | 
| jbe@309 | 176     if func then | 
| jbe@309 | 177       func() | 
| jbe@309 | 178       return true | 
| jbe/bsw@0 | 179     else | 
| jbe/bsw@0 | 180       return false | 
| jbe/bsw@0 | 181     end | 
| jbe/bsw@0 | 182   end | 
| jbe/bsw@0 | 183   function autoloader_mt.__index(self, key) | 
| jbe/bsw@0 | 184     local category, base_path, merge_base_path, file_key | 
| jbe/bsw@0 | 185     local merge = false | 
| jbe/bsw@0 | 186     if | 
| jbe/bsw@0 | 187       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and | 
| jbe/bsw@0 | 188       not string.find(key, "^__") | 
| jbe/bsw@0 | 189     then | 
| jbe/bsw@0 | 190       category        = "env" | 
| jbe@203 | 191       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/" | 
| jbe/bsw@0 | 192       merge           = true | 
| jbe@203 | 193       merge_base_path = WEBMCP_BASE_PATH .. "env/" | 
| jbe/bsw@0 | 194       file_key        = key | 
| jbe/bsw@0 | 195     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then | 
| jbe/bsw@0 | 196       category        = "model" | 
| jbe@203 | 197       base_path       = WEBMCP_BASE_PATH .. "model/" | 
| jbe/bsw@0 | 198       local first = true | 
| jbe/bsw@0 | 199       file_key = string.gsub(key, "[A-Z]", | 
| jbe/bsw@0 | 200         function(c) | 
| jbe/bsw@0 | 201           if first then | 
| jbe/bsw@0 | 202             first = false | 
| jbe/bsw@0 | 203             return string.lower(c) | 
| jbe/bsw@0 | 204           else | 
| jbe/bsw@0 | 205             return "_" .. string.lower(c) | 
| jbe/bsw@0 | 206           end | 
| jbe/bsw@0 | 207         end | 
| jbe/bsw@0 | 208       ) | 
| jbe/bsw@0 | 209     else | 
| jbe/bsw@0 | 210       return | 
| jbe/bsw@0 | 211     end | 
| jbe/bsw@0 | 212     local required_category = autoloader_category[self] | 
| jbe/bsw@0 | 213     if required_category and required_category ~= category then return end | 
| jbe@219 | 214     local path_fragment = autoloader_path[self] | 
| jbe@219 | 215     local path = base_path .. path_fragment .. file_key | 
| jbe@219 | 216     local merge_path | 
| jbe/bsw@0 | 217     if merge then | 
| jbe@219 | 218       merge_path = merge_base_path .. path_fragment .. file_key | 
| jbe/bsw@0 | 219     end | 
| jbe/bsw@0 | 220     local function try_dir(dirname) | 
| jbe/bsw@0 | 221       local dir = io.open(dirname) | 
| jbe/bsw@0 | 222       if dir then | 
| jbe/bsw@0 | 223         io.close(dir) | 
| jbe/bsw@0 | 224         local obj = {} | 
| jbe@219 | 225         install_autoloader(obj, category, path_fragment .. file_key .. "/") | 
| jbe/bsw@0 | 226         rawset(self, key, obj) | 
| jbe@219 | 227         try_exec(path .. "/__init.lua") | 
| jbe@219 | 228         if merge then try_exec(merge_path .. "/__init.lua") end | 
| jbe/bsw@0 | 229         return true | 
| jbe/bsw@0 | 230       else | 
| jbe/bsw@0 | 231         return false | 
| jbe/bsw@0 | 232       end | 
| jbe/bsw@0 | 233     end | 
| jbe@238 | 234     if self == _G then | 
| jbe@237 | 235       allowed_globals[key] = true | 
| jbe@233 | 236     end | 
| jbe@219 | 237     if merge and try_exec(merge_path .. ".lua") then | 
| jbe@219 | 238     elseif merge and try_dir(merge_path .. "/") then | 
| jbe@219 | 239     elseif try_exec(path .. ".lua") then | 
| jbe@219 | 240     elseif try_dir(path .. "/") then | 
| jbe/bsw@0 | 241     else end | 
| jbe@238 | 242     if self == _G then | 
| jbe@237 | 243       allowed_globals[key] = nil | 
| jbe@233 | 244     end | 
| jbe@237 | 245     return rawget(self, key) | 
| jbe/bsw@0 | 246   end | 
| jbe@219 | 247   install_autoloader(_G, nil, "") | 
| jbe@203 | 248   try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua") | 
| jbe@203 | 249   try_exec(WEBMCP_BASE_PATH .. "env/__init.lua") | 
| jbe/bsw@0 | 250 end | 
| jbe/bsw@0 | 251 | 
| jbe@214 | 252 -- replace Moonbridge listen function | 
| jbe@214 | 253 local moonbridge_listen = listen | 
| jbe@225 | 254 local listeners = {} | 
| jbe@214 | 255 function listen(args) | 
| jbe@214 | 256   listeners[#listeners+1] = args | 
| jbe@214 | 257 end | 
| jbe@214 | 258 | 
| jbe@203 | 259 -- prohibit (unintended) definition of new global variables | 
| jbe@237 | 260 function global_metatable.__newindex(self, key, value) | 
| jbe@237 | 261   if not allowed_globals[key] then | 
| jbe@237 | 262     error("Setting of global variable prohibited", 2) | 
| jbe@237 | 263   end | 
| jbe@239 | 264   _G[key] = value | 
| jbe@231 | 265 end | 
| jbe@203 | 266 | 
| jbe@220 | 267 -- execute configurations and pre-fork initializers | 
| jbe@206 | 268 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do | 
| jbe@206 | 269   execute.config(config_name) | 
| jbe@206 | 270 end | 
| jbe@220 | 271 execute.prefork_initializers() | 
| jbe@206 | 272 | 
| jbe@286 | 273 -- define post-fork initialization function (including loading of "multirand" library) | 
| jbe@286 | 274 local function postfork_init() | 
| jbe@286 | 275   _G.multirand = require "multirand" | 
| jbe@286 | 276   execute.postfork_initializers() | 
| jbe@286 | 277 end | 
| jbe@286 | 278 | 
| jbe/bsw@0 | 279 -- interactive console mode | 
| jbe@203 | 280 if WEBMCP_MODE == "interactive" then | 
| jbe@286 | 281   postfork_init() | 
| jbe@289 | 282   trace.disable()  -- avoids memory leakage | 
| jbe/bsw@0 | 283 end | 
| jbe/bsw@0 | 284 | 
| jbe@204 | 285 -- invoke moonbridge | 
| jbe@206 | 286 if WEBMCP_MODE == "listen" then | 
| jbe@264 | 287   local http_options = request.get_http_options() | 
| jbe@288 | 288   local min_requests_per_fork  = http_options.min_requests_per_fork or 50 | 
| jbe@288 | 289   local max_requests_per_fork  = http_options.max_requests_per_fork or 100 | 
| jbe@207 | 290   local http = require("moonbridge_http") | 
| jbe@211 | 291   for i, listener in ipairs(listeners) do | 
| jbe@264 | 292     local request_count = 0 | 
| jbe@266 | 293     local function inner_handler(http_request) | 
| jbe@264 | 294       request_count = request_count + 1 | 
| jbe@288 | 295       request.handler(http_request, request_count >= max_requests_per_fork) | 
| jbe@264 | 296     end | 
| jbe@264 | 297     local outer_handler = http.generate_handler(inner_handler, http_options) | 
| jbe@286 | 298     listener.prepare = postfork_init | 
| jbe@264 | 299     listener.connect = function(socket) | 
| jbe@264 | 300       outer_handler(socket) | 
| jbe@288 | 301       return request_count < min_requests_per_fork | 
| jbe@264 | 302     end | 
| jbe@211 | 303     listener.finish = execute.finalizers | 
| jbe@211 | 304     moonbridge_listen(listener) | 
| jbe@204 | 305   end | 
| jbe@204 | 306 end | 
| jbe@204 | 307 |