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