jbe@203: #!/usr/bin/env moonbridge jbe/bsw@0: jbe@278: WEBMCP_VERSION = "2.0.0" 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@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@203: jbe@206: -- configuration names are provided as 4th, 5th, etc. argument jbe@206: WEBMCP_CONFIG_NAMES = {select(4, ...)} 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/bsw@0: local file = io.open(filename, "r") jbe/bsw@0: if file then jbe/bsw@0: local filedata = file:read("*a") jbe/bsw@0: io.close(file) jbe@232: local func, errmsg = load(filedata, "=" .. filename, nil, _ENV) jbe/bsw@0: if func then jbe/bsw@0: func() jbe/bsw@0: return true jbe/bsw@0: else jbe/bsw@0: error(errmsg, 0) jbe/bsw@0: end 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/bsw@0: -- interactive console mode jbe@203: if WEBMCP_MODE == "interactive" then jbe@229: _G.multirand = require "multirand" -- TODO: cleaner solution jbe@215: execute.postfork_initializers() jbe@203: trace.disable() -- avoids memory leakage (TODO: needs general solution for moonbridge?) 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@264: local min_requests_per_connect = http_options.min_requests_per_connect or 90 jbe@264: local max_requests_per_connect = http_options.max_requests_per_connect 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@266: request.handler(http_request, request_count >= max_requests_per_connect) jbe@264: end jbe@264: local outer_handler = http.generate_handler(inner_handler, http_options) jbe@229: --listener.prepare = execute.postfork_initializers jbe@229: listener.prepare = function() jbe@229: _G.multirand = require "multirand" jbe@229: execute.postfork_initializers() jbe@229: end jbe@264: listener.connect = function(socket) jbe@264: outer_handler(socket) jbe@264: return request_count < min_requests_per_connect jbe@264: end jbe@211: listener.finish = execute.finalizers jbe@211: moonbridge_listen(listener) jbe@204: end jbe@204: end jbe@204: