jbe/bsw@0: #!/usr/bin/env lua jbe/bsw@0: jbe/bsw@0: -- include "../lib/" in search path for libraries jbe/bsw@0: do jbe/bsw@0: package.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/bsw@0: paths[#paths+1] = "../accelerator/?." .. ext jbe/bsw@0: end jbe/bsw@0: for ext in pairs(slib_exts) do jbe/bsw@0: paths[#paths+1] = "../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/bsw@0: -- load os extensions for lua jbe/bsw@0: -- (should happen as soon as possible due to run time measurement) jbe/bsw@0: require 'extos' jbe/bsw@0: jbe/bsw@0: -- load nihil library jbe/bsw@0: require 'nihil' jbe/bsw@0: jbe/bsw@0: -- load random generator library jbe/bsw@0: require 'multirand' jbe/bsw@0: jbe/bsw@0: -- load rocketcgi library and map it to cgi jbe/bsw@0: do jbe/bsw@0: local option = os.getenv("WEBMCP_INTERACTIVE") jbe/bsw@0: if option and option ~= "" and option ~= "0" then jbe/bsw@0: cgi = nil jbe/bsw@0: else jbe/bsw@0: require 'rocketcgi' jbe/bsw@0: cgi = rocketcgi jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: -- load database access library with object relational mapper jbe/bsw@0: require 'mondelefant' jbe/bsw@0: mondelefant.connection_prototype.error_objects = true jbe/bsw@0: jbe/bsw@0: -- load type system "atom" jbe/bsw@0: require 'atom' jbe/bsw@0: jbe/bsw@0: -- load mondelefant atom connector jbe/bsw@0: require 'mondelefant_atom_connector' jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: cloned_table = -- newly generated table jbe/bsw@0: table.new( jbe/bsw@0: table_or_nil -- keys of a given table will be copied to the new table jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: If a table is given, then a cloned table is returned. jbe/bsw@0: If nil is given, then a new empty table is returned. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function table.new(tbl) jbe/bsw@0: new_tbl = {} jbe/bsw@0: if tbl then jbe/bsw@0: for key, value in pairs(tbl) do jbe/bsw@0: new_tbl[key] = value jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: return new_tbl jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: at_exit( jbe/bsw@0: func -- function to be called before the process is ending jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: Registers a function to be called before the CGI process is exiting. jbe/bsw@0: --]]-- jbe/bsw@0: do jbe/bsw@0: local exit_handlers = {} jbe/bsw@0: function at_exit(func) jbe/bsw@0: table.insert(exit_handlers, func) jbe/bsw@0: end jbe/bsw@0: function exit(code) jbe/bsw@0: for i = #exit_handlers, 1, -1 do jbe/bsw@0: exit_handlers[i]() jbe/bsw@0: end jbe/bsw@0: os.exit(code) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: app -- table to store an application state jbe/bsw@0: jbe/bsw@0: 'app' is a global table for storing any application state data jbe/bsw@0: --]]-- jbe/bsw@0: app = {} jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: config -- table to store application configuration jbe/bsw@0: jbe/bsw@0: 'config' is a global table, which can be modified by a config file of an application to modify the behaviour of that application. jbe/bsw@0: --]]-- jbe/bsw@0: config = {} jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: -- autoloader system for WebMCP environment "../env/", jbe/bsw@0: -- application environment extensions "$WEBMCP_APP_BASE/env/" jbe/bsw@0: -- and models "$WEBMCP_APP_BASE/model/" jbe/bsw@0: do jbe/bsw@0: local app_base = os.getenv("WEBMCP_APP_BASEPATH") jbe/bsw@0: if not app_base then jbe/bsw@0: error( jbe/bsw@0: "Failed to initialize autoloader " .. jbe/bsw@0: "due to unset WEBMCP_APP_BASEPATH environment variable." jbe/bsw@0: ) jbe/bsw@0: end 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/bsw@0: local func, errmsg = loadstring(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/bsw@0: return string.gsub( jbe/bsw@0: base .. table.concat(path, "/") .. "/" .. key, "/+", "/" jbe/bsw@0: ) 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/bsw@0: base_path = "../env/" jbe/bsw@0: merge = true jbe/bsw@0: merge_base_path = app_base .. "/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/bsw@0: base_path = app_base .. "/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/bsw@0: for i, v in ipairs(path) do sub_path[i] = v end jbe/bsw@0: table.insert(sub_path, 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/bsw@0: try_exec("../env/__init.lua") jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: -- interactive console mode jbe/bsw@0: if not cgi then 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: return jbe/bsw@0: end jbe/bsw@0: 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/bsw@0: if cgi.params["_webmcp_404"] then jbe/bsw@0: request.force_absolute_baseurl() 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/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/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/bsw@0: params = routing_params 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/bsw@0: request.process_forward() jbe/bsw@0: execute.filtered_view{ jbe/bsw@0: module = request.get_module(), jbe/bsw@0: view = request.get_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/bsw@0: trace.exectime{ real = os.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@0: if not request.get_status() 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@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/bsw@0: params = redirect_params 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/bsw@0: -- ajax jbe/bsw@0: if request.is_ajax() then jbe/bsw@0: cgi.set_content_type('text/html') jbe/bsw@0: for i, slot_ident in ipairs{'main', 'actions', 'title', 'topnav', 'sidenav', 'debug', 'notice', 'warning', 'error'} do jbe/bsw@0: local html = slot.get_content(slot_ident) jbe/bsw@0: if html then jbe/bsw@0: cgi.send_data("document.getElementById('" .. slot_ident .. "').innerHTML=" .. encode.json(html or ' ') .. ";") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: -- oder ganz herkoemmlich 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/bsw@0: exit()