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