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@317: --[[-- jbe@317: _G jbe@317: jbe@317: 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: jbe@317: 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: jbe@317: --]]-- jbe@231: local _G = _G jbe@237: local allowed_globals = {} jbe@324: local protected_environment = setmetatable( jbe@324: {}, -- proxy environment used all chunks loaded through loadcached(...) jbe@324: { jbe@324: __index = _G, jbe@324: __newindex = function(self, key, value) jbe@324: if allowed_globals[key] then jbe@324: _G[key] = value jbe@317: else jbe@324: if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then jbe@333: error('Attempt to set global variable "' .. key .. '" (Hint: missing local statement? Use _G.' .. key .. '= to really set global variable.)', 2) jbe@324: else jbe@324: error('Attempt to set global variable', 2) jbe@324: end jbe@317: end jbe@317: end jbe@324: } jbe@324: ) jbe@318: --//-- 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@311: local cached_func = cache[filename] jbe@311: if cached_func then jbe@311: return cached_func jbe@311: end 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@324: local func, compile_error = load(filedata, "=" .. filename, nil, protected_environment) jbe@309: if func then jbe@309: cache[filename] = func jbe@309: return func jbe@309: end jbe@311: error(compile_error, 0) 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@323: if _MOONBRIDGE_VERSION then 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@316: elseif #WEBMCP_CONFIG_NAMES < 1 then jbe@203: helpout = io.stderr jbe@203: end jbe@203: if helpout then jbe@316: helpout:write("Usage: moonbridge [moonbr opts] -- /bin/mcp.lua [ ...]\n") jbe@316: helpout:write(" or: lua -i [Lua opts] -- /bin/mcp.lua [ ...]\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@316: WEBMCP_APP_NAME = arg3 jbe@68: end jbe@1: jbe@313: -- check if framework path is correct jbe@313: do jbe@313: local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r") jbe@313: if not file then jbe@313: error('Could not find "webmcp_version" file: ' .. errmsg, 0) jbe@313: end jbe@313: local version = assert(file:read()) jbe@313: assert(file:close()) jbe@313: if version ~= WEBMCP_VERSION then jbe@313: error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"') jbe@313: end jbe@313: end jbe@313: 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@324: try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua") jbe@324: try_exec(WEBMCP_BASE_PATH .. "env/__init.lua") jbe@214: end jbe@214: jbe@286: -- define post-fork initialization function (including loading of "multirand" library) jbe@286: local function postfork_init() jbe@324: multirand = require "multirand" jbe@286: execute.postfork_initializers() jbe@286: end jbe@286: jbe@317: -- prepare for interactive or listen mode jbe@203: if WEBMCP_MODE == "interactive" then jbe@317: function listen() -- overwrite Moonbridge's listen function jbe@317: -- ignore listen function calls for interactive mode jbe@317: end jbe@317: trace.disable() -- avoids memory leakage when scripts are running endlessly jbe@317: else jbe@317: local moonbridge_listen = listen jbe@207: local http = require("moonbridge_http") jbe@317: function listen(args) -- overwrite Moonbridge's listen function jbe@322: assert(args, "No argument passed to listen function") jbe@322: local min_requests_per_fork = args.min_requests_per_fork or 50 jbe@322: local max_requests_per_fork = args.max_requests_per_fork or 100 jbe@316: local interval_handlers = {} jbe@317: for j, listener in ipairs(args) do jbe@317: if listener.proto == "interval" then jbe@317: local name = listener.name or "Unnamed interval #" .. #interval_handlers+1 jbe@317: interval_handlers[name] = listener.handler jbe@317: listener.name = name jbe@316: end jbe@316: end jbe@264: local request_count = 0 jbe@266: local function inner_handler(http_request) jbe@328: request_count = request_count + 1 jbe@328: if request_count >= max_requests_per_fork then jbe@328: http_request:close_after_finish() jbe@328: end jbe@317: request.initialize() jbe@328: return request.handler(http_request) jbe@264: end jbe@326: local outer_handler = http.generate_handler(inner_handler, args.http_options) jbe@317: args.prepare = postfork_init jbe@317: args.connect = function(socket) jbe@316: if socket.interval then jbe@328: request_count = request_count + 1 jbe@317: request.initialize() jbe@316: interval_handlers[socket.interval]() jbe@316: else jbe@327: local success = outer_handler(socket) jbe@327: if not success then jbe@327: return false jbe@327: end jbe@316: end jbe@288: return request_count < min_requests_per_fork jbe@264: end jbe@317: args.finish = execute.finalizers jbe@317: moonbridge_listen(args) jbe@204: end jbe@204: end jbe@317: jbe@317: -- execute configurations and pre-fork initializers jbe@317: for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do jbe@317: execute.config(config_name) jbe@317: end jbe@317: execute.prefork_initializers() jbe@317: jbe@324: -- perform post-fork initializations (once) in case of interactive mode jbe@317: if WEBMCP_MODE == "interactive" then jbe@317: postfork_init() jbe@317: end