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