jbe@203: #!/usr/bin/env moonbridge jbe/bsw@0: jbe@294: --[[-- jbe@294: WEBMCP_VERSION jbe@294: jbe@294: A string containing the WebMCP version, e.g. "2.0.0" jbe@294: --]]-- jbe@278: WEBMCP_VERSION = "2.0.0" jbe@294: --//-- jbe@64: jbe@231: -- allow control of global environment jbe@231: local _G = _G jbe@237: local allowed_globals = {} jbe@231: local global_metatable = { jbe@231: __index = _G, jbe@231: __newindex = function(self, key, value) jbe@231: _G[key] = value jbe@231: end jbe@231: } jbe@231: _ENV = setmetatable({}, global_metatable) jbe@231: jbe@294: --[[-- jbe@309: lua_func = -- compiled Lua function jbe@309: loadcached( jbe@309: filename -- path to a Lua source or byte-code file jbe@309: ) jbe@309: jbe@309: 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: jbe@309: --]]-- jbe@309: do jbe@309: local cache = {} jbe@309: function loadcached(filename) jbe@309: local file, read_error = io.open(filename, "r") jbe@309: if file then jbe@309: local filedata = assert(file:read("*a")) jbe@309: assert(file:close()) jbe@309: local func, compile_error = load(filedata, "=" .. filename, nil, _ENV) jbe@309: if func then jbe@309: cache[filename] = func jbe@309: return func jbe@309: else jbe@309: error(compile_error, 0) jbe@309: end jbe@309: else jbe@309: return nil, read_error jbe@309: end jbe@309: end jbe@309: end jbe@309: --//-- jbe@309: jbe@309: --[[-- jbe@294: WEBMCP_MODE jbe@294: jbe@294: A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode. jbe@294: --]]-- jbe@203: -- check if interactive mode jbe@203: if listen then -- defined by moonbridge jbe@203: WEBMCP_MODE = "listen" jbe@203: else jbe@203: WEBMCP_MODE = "interactive" jbe@64: end jbe@294: --//-- jbe@203: jbe@294: --[[-- jbe@294: WEBMCP_CONFIG_NAMES jbe@294: jbe@294: A list of the selected configuration names. jbe@294: --]]-- jbe@294: -- configuration names are provided as 4th, 5th, etc. command line argument jbe@206: WEBMCP_CONFIG_NAMES = {select(4, ...)} jbe@294: --//-- jbe@294: jbe@294: --[[-- jbe@294: WEBMCP_FRAMEWORK_PATH jbe@294: jbe@294: Directory of the WebMCP framework (always includes a trailing slash). jbe@294: --]]-- jbe@294: -- set in mcp.lua jbe@294: --//-- jbe@294: jbe@294: --[[-- jbe@294: WEBMCP_BASE_PATH jbe@294: jbe@294: Base directory of the application (always includes a trailing slash). jbe@294: --]]-- jbe@294: -- set in mcp.lua jbe@294: --//-- jbe@294: jbe@294: --[[-- jbe@294: WEBMCP_APP_NAME jbe@294: jbe@294: Application name (usually "main"). May be nil in case of interactive mode. jbe@294: --]]-- jbe@294: -- set in mcp.lua jbe@294: --//-- jbe@203: jbe@203: -- determine framework and bath path from command line arguments jbe@203: -- or print usage synopsis (if applicable) jbe@68: do jbe@206: local arg1, arg2, arg3 = ... jbe@203: local helpout jbe@203: if jbe@203: arg1 == "-h" or arg1 == "--help" or jbe@206: arg2 == "-h" or arg2 == "--help" -- if first arg is provided by wrapper jbe@203: then jbe@203: helpout = io.stdout jbe@203: elseif jbe@217: #WEBMCP_CONFIG_NAMES < 1 or jbe@206: (WEBMCP_MODE == "interactive") ~= (arg3 == "INTERACTIVE") jbe@203: then jbe@203: helpout = io.stderr jbe@203: end jbe@203: if helpout then jbe@217: helpout:write("Usage: moonbridge -- /bin/mcp.lua [ ...]\n") jbe@217: helpout:write(" or: lua -i /bin/mcp.lua INTERACTIVE [ ...]\n") jbe@203: if helpout == io.stderr then jbe@203: return 1 jbe@68: else jbe@203: return 0 jbe@68: end jbe@68: end jbe@203: local function append_trailing_slash(str) jbe@217: return string.gsub(str, "([^/])$", function(last) return last .. "/" end) jbe@203: end jbe@203: WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1) jbe@203: WEBMCP_BASE_PATH = append_trailing_slash(arg2) jbe@206: if WEBMCP_MODE == "listen" then jbe@206: WEBMCP_APP_NAME = arg3 jbe@206: end jbe@68: end jbe@1: jbe@203: -- setup search paths for libraries jbe/bsw@0: do jbe@217: if string.match(package.path, "^[^;]") then jbe@217: package.path = ";" .. package.path jbe@217: end jbe@217: package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path jbe/bsw@0: -- find out which file name extension shared libraries have jbe/bsw@0: local slib_exts = {} jbe/bsw@0: for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do jbe@217: if not slib_exts[ext] then jbe@217: slib_exts[#slib_exts+1] = ext jbe@217: slib_exts[ext] = true jbe@217: end jbe/bsw@0: end jbe/bsw@0: local paths = {} jbe@217: for i, ext in ipairs(slib_exts) do jbe@203: paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext jbe/bsw@0: end jbe@217: for i, ext in ipairs(slib_exts) do jbe@203: paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext jbe/bsw@0: end jbe/bsw@0: paths[#paths+1] = package.cpath jbe/bsw@0: package.cpath = table.concat(paths, ";") jbe/bsw@0: end jbe/bsw@0: jbe@203: -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/", jbe@203: -- application environment extensions "$WEBMCP_BASE_PATH/env/" jbe@203: -- and models "$WEBMCP_BASE_PATH/model/" jbe/bsw@0: do jbe/bsw@0: local weakkey_mt = { __mode = "k" } jbe/bsw@0: local autoloader_category = setmetatable({}, weakkey_mt) jbe/bsw@0: local autoloader_path = setmetatable({}, weakkey_mt) jbe/bsw@0: local autoloader_mt = {} jbe@219: local function install_autoloader(self, category, path_fragment) jbe/bsw@0: autoloader_category[self] = category jbe@219: autoloader_path[self] = path_fragment jbe/bsw@0: setmetatable(self, autoloader_mt) jbe/bsw@0: end jbe/bsw@0: local function try_exec(filename) jbe@309: local func = loadcached(filename) jbe@309: if func then jbe@309: func() jbe@309: return true jbe/bsw@0: else jbe/bsw@0: return false jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: function autoloader_mt.__index(self, key) jbe/bsw@0: local category, base_path, merge_base_path, file_key jbe/bsw@0: local merge = false jbe/bsw@0: if jbe/bsw@0: string.find(key, "^[a-z_][A-Za-z0-9_]*$") and jbe/bsw@0: not string.find(key, "^__") jbe/bsw@0: then jbe/bsw@0: category = "env" jbe@203: base_path = WEBMCP_FRAMEWORK_PATH .. "env/" jbe/bsw@0: merge = true jbe@203: merge_base_path = WEBMCP_BASE_PATH .. "env/" jbe/bsw@0: file_key = key jbe/bsw@0: elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then jbe/bsw@0: category = "model" jbe@203: base_path = WEBMCP_BASE_PATH .. "model/" jbe/bsw@0: local first = true jbe/bsw@0: file_key = string.gsub(key, "[A-Z]", jbe/bsw@0: function(c) jbe/bsw@0: if first then jbe/bsw@0: first = false jbe/bsw@0: return string.lower(c) jbe/bsw@0: else jbe/bsw@0: return "_" .. string.lower(c) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: ) jbe/bsw@0: else jbe/bsw@0: return jbe/bsw@0: end jbe/bsw@0: local required_category = autoloader_category[self] jbe/bsw@0: if required_category and required_category ~= category then return end jbe@219: local path_fragment = autoloader_path[self] jbe@219: local path = base_path .. path_fragment .. file_key jbe@219: local merge_path jbe/bsw@0: if merge then jbe@219: merge_path = merge_base_path .. path_fragment .. file_key jbe/bsw@0: end jbe/bsw@0: local function try_dir(dirname) jbe/bsw@0: local dir = io.open(dirname) jbe/bsw@0: if dir then jbe/bsw@0: io.close(dir) jbe/bsw@0: local obj = {} jbe@219: install_autoloader(obj, category, path_fragment .. file_key .. "/") jbe/bsw@0: rawset(self, key, obj) jbe@219: try_exec(path .. "/__init.lua") jbe@219: if merge then try_exec(merge_path .. "/__init.lua") end jbe/bsw@0: return true jbe/bsw@0: else jbe/bsw@0: return false jbe/bsw@0: end jbe/bsw@0: end jbe@238: if self == _G then jbe@237: allowed_globals[key] = true jbe@233: end jbe@219: if merge and try_exec(merge_path .. ".lua") then jbe@219: elseif merge and try_dir(merge_path .. "/") then jbe@219: elseif try_exec(path .. ".lua") then jbe@219: elseif try_dir(path .. "/") then jbe/bsw@0: else end jbe@238: if self == _G then jbe@237: allowed_globals[key] = nil jbe@233: end jbe@237: return rawget(self, key) jbe/bsw@0: end jbe@219: install_autoloader(_G, nil, "") jbe@203: try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua") jbe@203: try_exec(WEBMCP_BASE_PATH .. "env/__init.lua") jbe/bsw@0: end jbe/bsw@0: jbe@214: -- replace Moonbridge listen function jbe@214: local moonbridge_listen = listen jbe@225: local listeners = {} jbe@214: function listen(args) jbe@214: listeners[#listeners+1] = args jbe@214: end jbe@214: jbe@203: -- prohibit (unintended) definition of new global variables jbe@237: function global_metatable.__newindex(self, key, value) jbe@237: if not allowed_globals[key] then jbe@237: error("Setting of global variable prohibited", 2) jbe@237: end jbe@239: _G[key] = value jbe@231: end jbe@203: jbe@220: -- execute configurations and pre-fork initializers jbe@206: for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do jbe@206: execute.config(config_name) jbe@206: end jbe@220: execute.prefork_initializers() jbe@206: jbe@286: -- define post-fork initialization function (including loading of "multirand" library) jbe@286: local function postfork_init() jbe@286: _G.multirand = require "multirand" jbe@286: execute.postfork_initializers() jbe@286: end jbe@286: jbe/bsw@0: -- interactive console mode jbe@203: if WEBMCP_MODE == "interactive" then jbe@286: postfork_init() jbe@289: trace.disable() -- avoids memory leakage jbe/bsw@0: end jbe/bsw@0: jbe@204: -- invoke moonbridge jbe@206: if WEBMCP_MODE == "listen" then jbe@264: local http_options = request.get_http_options() jbe@288: local min_requests_per_fork = http_options.min_requests_per_fork or 50 jbe@288: local max_requests_per_fork = http_options.max_requests_per_fork or 100 jbe@207: local http = require("moonbridge_http") jbe@211: for i, listener in ipairs(listeners) do jbe@264: local request_count = 0 jbe@266: local function inner_handler(http_request) jbe@264: request_count = request_count + 1 jbe@288: request.handler(http_request, request_count >= max_requests_per_fork) jbe@264: end jbe@264: local outer_handler = http.generate_handler(inner_handler, http_options) jbe@286: listener.prepare = postfork_init jbe@264: listener.connect = function(socket) jbe@264: outer_handler(socket) jbe@288: return request_count < min_requests_per_fork jbe@264: end jbe@211: listener.finish = execute.finalizers jbe@211: moonbridge_listen(listener) jbe@204: end jbe@204: end jbe@204: