jbe@203: #!/usr/bin/env moonbridge jbe/bsw@0: jbe@203: WEBMCP_VERSION = "2.0.0_devel" jbe@64: 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@206: #config_args < 1 or jbe@206: (WEBMCP_MODE == "interactive") ~= (arg3 == "INTERACTIVE") jbe@203: then jbe@203: helpout = io.stderr jbe@203: end jbe@206: helpout:write("Usage: moonbridge -- /bin/mcp.lua [ ...]\n") jbe@206: helpout:write(" or: lua -i /bin/mcp.lua INTERACTIVE [ ...]\n") jbe@203: if helpout then 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@203: return string.sub(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@203: 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/bsw@0: slib_exts[ext] = true jbe/bsw@0: end jbe/bsw@0: local paths = {} jbe/bsw@0: for ext in pairs(slib_exts) do jbe@203: paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext jbe/bsw@0: end jbe/bsw@0: for ext in pairs(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/bsw@0: local function install_autoloader(self, category, path) jbe/bsw@0: autoloader_category[self] = category jbe/bsw@0: autoloader_path[self] = path 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@68: local func, errmsg = load(filedata, "=" .. filename) 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: local function compose_path_string(base, path, key) jbe@203: if #path == 0 then jbe@203: return base .. "/" .. key jbe@203: else jbe@203: return base .. table.concat(path, "/") .. "/" .. key jbe@203: 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/bsw@0: local path = autoloader_path[self] jbe/bsw@0: local path_string = compose_path_string(base_path, path, file_key) jbe/bsw@0: local merge_path_string jbe/bsw@0: if merge then jbe/bsw@0: merge_path_string = compose_path_string( jbe/bsw@0: merge_base_path, path, file_key jbe/bsw@0: ) 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/bsw@0: local sub_path = {} jbe@203: for i = 1, #path do sub_path[i] = path[i] end jbe@203: sub_path[#path+1] = file_key jbe/bsw@0: install_autoloader(obj, category, sub_path) jbe/bsw@0: rawset(self, key, obj) jbe/bsw@0: try_exec(path_string .. "/__init.lua") jbe/bsw@0: if merge then try_exec(merge_path_string .. "/__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/bsw@0: if merge and try_exec(merge_path_string .. ".lua") then jbe/bsw@0: elseif merge and try_dir(merge_path_string .. "/") then jbe/bsw@0: elseif try_exec(path_string .. ".lua") then jbe/bsw@0: elseif try_dir(path_string .. "/") then jbe/bsw@0: else end jbe/bsw@0: return rawget(self, key) jbe/bsw@0: end jbe/bsw@0: 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@203: -- prohibit (unintended) definition of new global variables jbe@203: _ENV = setmetatable({}, { jbe@203: __index = _G, jbe@203: __newindex = function() jbe@203: error("Setting of global variable prohibited") jbe@203: end jbe@203: }) jbe@203: jbe@206: -- execute configurations jbe@206: for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do jbe@206: execute.config(config_name) jbe@206: end jbe@206: jbe/bsw@0: -- interactive console mode jbe@203: if WEBMCP_MODE == "interactive" then 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@207: local http = require("moonbridge_http") jbe@206: local moonbridge_listen = listen jbe@206: local listeners jbe@206: function _G.listen(args) jbe@206: listeners[#listeners+1] = args jbe@204: end jbe@206: for i = 1, #extraargs/2 do jbe@206: local config = {} jbe@206: listeners = {} jbe@206: execute.config(config_name) jbe@206: for i, listener in ipairs(listeners) do jbe@206: listener.prepare = execute.prefork_initializers jbe@206: listener.connect = http.generate_handler( jbe@206: request.get_http_options(), jbe@206: function(req) jbe@206: execute.postfork_initializers() jbe@206: request.handler(req) jbe@206: end jbe@206: ) jbe@206: --listener.finish = ??? -- TODO: requires coroutines and execute.inner() for initializers? jbe@206: moonbridge_listen(listener) jbe@204: end jbe@204: end jbe@204: end jbe@204: jbe@206: --[[ TODO: following lines to be moved to execute.server(...) jbe@203: jbe/bsw@0: local success, error_info = xpcall( jbe/bsw@0: function() jbe/bsw@0: jbe/bsw@0: -- execute configuration file jbe/bsw@0: do jbe/bsw@0: local config_name = request.get_config_name() jbe/bsw@0: if config_name then jbe/bsw@0: execute.config(config_name) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: -- restore slots if coming from http redirect jbe/bsw@0: if cgi.params.tempstore then jbe/bsw@0: trace.restore_slots{} jbe/bsw@0: local blob = tempstore.pop(cgi.params.tempstore) jbe/bsw@0: if blob then slot.restore_all(blob) end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local function file_exists(filename) jbe/bsw@0: local file = io.open(filename, "r") jbe/bsw@0: if file then jbe/bsw@0: io.close(file) jbe/bsw@0: return true jbe/bsw@0: else jbe/bsw@0: return false jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe@97: if request.is_404() then jbe/bsw@0: request.set_status("404 Not Found") jbe/bsw@0: if request.get_404_route() then jbe/bsw@0: request.forward(request.get_404_route()) jbe/bsw@0: else jbe/bsw@0: error("No 404 page set.") jbe/bsw@0: end jbe/bsw@0: elseif request.get_action() then jbe/bsw@0: trace.request{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: action = request.get_action() jbe/bsw@0: } jbe/bsw@0: if jbe/bsw@0: request.get_404_route() and jbe/bsw@0: not file_exists( jbe/bsw@0: encode.action_file_path{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: action = request.get_action() jbe/bsw@0: } jbe/bsw@0: ) jbe/bsw@0: then jbe/bsw@0: request.set_status("404 Not Found") jbe/bsw@0: request.forward(request.get_404_route()) jbe/bsw@0: else jbe/bsw@0: if cgi.method ~= "POST" then jbe/bsw@0: request.set_status("405 Method Not Allowed") jbe/bsw@0: cgi.add_header("Allow: POST") jbe/bsw@0: error("Tried to invoke an action with a GET request.") jbe/bsw@0: end jbe/bsw@0: local action_status = execute.filtered_action{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: action = request.get_action(), jbe/bsw@0: } jbe/bsw@0: if not request.is_rerouted() then jbe/bsw@0: local routing_mode, routing_module, routing_view jbe/bsw@0: routing_mode = cgi.params["_webmcp_routing." .. action_status .. ".mode"] jbe/bsw@0: routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"] jbe/bsw@0: routing_view = cgi.params["_webmcp_routing." .. action_status .. ".view"] jbe@113: routing_anchor = cgi.params["_webmcp_routing." .. action_status .. ".anchor"] jbe/bsw@0: if not (routing_mode or routing_module or routing_view) then jbe/bsw@0: action_status = "default" jbe/bsw@0: routing_mode = cgi.params["_webmcp_routing.default.mode"] jbe/bsw@0: routing_module = cgi.params["_webmcp_routing.default.module"] jbe/bsw@0: routing_view = cgi.params["_webmcp_routing.default.view"] jbe@113: routing_anchor = cgi.params["_webmcp_routing.default.anchor"] jbe/bsw@0: end jbe/bsw@0: assert(routing_module, "Routing information has no module.") jbe/bsw@0: assert(routing_view, "Routing information has no view.") jbe/bsw@0: if routing_mode == "redirect" then jbe/bsw@0: local routing_params = {} jbe/bsw@0: for key, value in pairs(cgi.params) do jbe/bsw@0: local status, stripped_key = string.match( jbe/bsw@0: key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" jbe/bsw@0: ) jbe/bsw@0: if status == action_status then jbe/bsw@0: routing_params[stripped_key] = value jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: request.redirect{ jbe/bsw@0: module = routing_module, jbe/bsw@0: view = routing_view, jbe/bsw@0: id = cgi.params["_webmcp_routing." .. action_status .. ".id"], jbe@113: params = routing_params, jbe@113: anchor = routing_anchor jbe/bsw@0: } jbe/bsw@0: elseif routing_mode == "forward" then jbe/bsw@0: request.forward{ module = routing_module, view = routing_view } jbe/bsw@0: else jbe/bsw@0: error("Missing or unknown routing mode in request parameters.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: -- no action jbe/bsw@0: trace.request{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: view = request.get_view() jbe/bsw@0: } jbe/bsw@0: if jbe/bsw@0: request.get_404_route() and jbe/bsw@0: not file_exists( jbe/bsw@0: encode.view_file_path{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: view = request.get_view() jbe/bsw@0: } jbe/bsw@0: ) jbe/bsw@0: then jbe/bsw@0: request.set_status("404 Not Found") jbe/bsw@0: request.forward(request.get_404_route()) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if not request.get_redirect_data() then jbe@3: request.process_forward() jbe/bsw@2: local view = request.get_view() jbe/bsw@2: if string.find(view, "^_") then jbe/bsw@2: error("Tried to call a private view (prefixed with underscore).") jbe/bsw@2: end jbe/bsw@0: execute.filtered_view{ jbe/bsw@0: module = request.get_module(), jbe/bsw@2: view = view, jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: -- force error due to missing absolute base URL until its too late to display error message jbe/bsw@0: --if request.get_redirect_data() then jbe/bsw@0: -- request.get_absolute_baseurl() jbe/bsw@0: --end jbe/bsw@0: jbe/bsw@0: end, jbe/bsw@0: jbe/bsw@0: function(errobj) jbe/bsw@0: return { jbe/bsw@0: errobj = errobj, jbe/bsw@0: stacktrace = string.gsub( jbe/bsw@0: debug.traceback('', 2), jbe/bsw@0: "^\r?\n?stack traceback:\r?\n?", "" jbe/bsw@0: ) jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: if not success then trace.error{} end jbe/bsw@0: jbe/bsw@0: -- laufzeitermittlung jbe@65: trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() } jbe/bsw@0: jbe/bsw@0: slot.select('trace', trace.render) -- render trace information jbe/bsw@0: jbe/bsw@0: local redirect_data = request.get_redirect_data() jbe/bsw@0: jbe/bsw@0: -- log error and switch to error layout, unless success jbe/bsw@0: if not success then jbe/bsw@0: local errobj = error_info.errobj jbe/bsw@0: local stacktrace = error_info.stacktrace jbe/bsw@11: if not request.get_status() and not request.get_json_request_slots() then jbe/bsw@0: request.set_status("500 Internal Server Error") jbe/bsw@0: end jbe/bsw@0: slot.set_layout('system_error') jbe/bsw@0: slot.select('system_error', function() jbe/bsw@0: if getmetatable(errobj) == mondelefant.errorobject_metatable then jbe/bsw@0: slot.put( jbe/bsw@0: "

Database error of class ", jbe/bsw@0: encode.html(errobj.code), jbe/bsw@0: " occured:
", jbe/bsw@0: encode.html(errobj.message), jbe/bsw@0: "

" jbe/bsw@0: ) jbe/bsw@0: else jbe/bsw@0: slot.put("

", encode.html(tostring(errobj)), "

") jbe/bsw@0: end jbe/bsw@0: slot.put("

Stack trace follows:
") jbe/bsw@0: slot.put(encode.html_newlines(encode.html(stacktrace))) jbe/bsw@0: slot.put("

") jbe/bsw@0: end) jbe/bsw@0: elseif redirect_data then jbe/bsw@0: local redirect_params = {} jbe/bsw@0: for key, value in pairs(redirect_data.params) do jbe/bsw@0: redirect_params[key] = value jbe/bsw@0: end jbe/bsw@0: local slot_dump = slot.dump_all() jbe/bsw@0: if slot_dump ~= "" then jbe/bsw@0: redirect_params.tempstore = tempstore.save(slot_dump) jbe/bsw@0: end jbe/bsw@11: local json_request_slots = request.get_json_request_slots() jbe/bsw@11: if json_request_slots then jbe/bsw@11: redirect_params["_webmcp_json_slots[]"] = json_request_slots jbe/bsw@11: end jbe/bsw@0: cgi.redirect( jbe/bsw@0: encode.url{ jbe/bsw@0: base = request.get_absolute_baseurl(), jbe/bsw@0: module = redirect_data.module, jbe/bsw@0: view = redirect_data.view, jbe/bsw@0: id = redirect_data.id, jbe@113: params = redirect_params, jbe@113: anchor = redirect_data.anchor jbe/bsw@0: } jbe/bsw@0: ) jbe/bsw@0: cgi.send_data() jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if not success or not redirect_data then jbe/bsw@0: jbe/bsw@0: local http_status = request.get_status() jbe/bsw@0: if http_status then jbe/bsw@0: cgi.set_status(http_status) jbe/bsw@0: end jbe/bsw@0: jbe@1: local json_request_slots = request.get_json_request_slots() jbe@1: if json_request_slots then jbe@1: cgi.set_content_type('application/json') jbe@1: local data = {} jbe@1: for idx, slot_ident in ipairs(json_request_slots) do jbe@1: data[slot_ident] = slot.get_content(slot_ident) jbe/bsw@0: end jbe@1: cgi.send_data(encode.json(data)) jbe/bsw@0: else jbe/bsw@0: cgi.set_content_type(slot.get_content_type()) jbe/bsw@0: cgi.send_data(slot.render_layout()) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe@204: --]] jbe@204: