webmcp
view framework/bin/mcp.lua @ 203:c6ef9991b911
Removed CGI support; Created draft for new MCP program to be used with "Moonbridge Network Server for Lua Applications"
| author | jbe | 
|---|---|
| date | Fri Jan 09 01:57:20 2015 +0100 (2015-01-09) | 
| parents | framework/cgi-bin/webmcp.lua@8d7665e0d490 | 
| children | b059efd81649 | 
 line source
     1 #!/usr/bin/env moonbridge
     3 WEBMCP_VERSION = "2.0.0_devel"
     5 -- check if interactive mode
     6 if listen then  -- defined by moonbridge
     7   WEBMCP_MODE = "listen"
     8 else
     9   WEBMCP_MODE = "interactive"
    10 end
    12 -- store extra command line arguments
    13 local extraargs = {select(3, ...)}
    15 -- determine framework and bath path from command line arguments
    16 -- or print usage synopsis (if applicable)
    17 do
    18   local arg1, arg2 = ...
    19   local helpout
    20   if
    21     arg1 == "-h" or arg1 == "--help" or
    22     arg2 == "-h" or arg2 == "--help"
    23   then
    24     helpout = io.stdout
    25   elseif
    26     (WEBMCP_MODE == "listen" and (#extraargs < 2 or #extraargs % 2 ~= 0)) or
    27     (WEBMCP_MODE == "interactive" and arg2 == nil)
    28   then
    29     helpout = io.stderr
    30   end
    31   helpout:write("Usage: moonbridge -- mcp.lua <framework path> <app base path> <app name 1> <config name 1> [<app name 2> <config name 2> ...]\n")
    32   helpout:write("   or: lua -i mcp.lua <framework path> <app base path> [<config name>]\n")
    33   if helpout then
    34     if helpout == io.stderr then
    35       return 1
    36     else
    37       return 0
    38     end
    39   end
    40   local function append_trailing_slash(str)
    41     return string.sub(str, "([^/])$", function(last) return last .. "/" end)
    42   end
    43   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
    44   WEBMCP_BASE_PATH      = append_trailing_slash(arg2)
    45 end
    47 -- setup search paths for libraries
    48 do
    49   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua;" .. package.path
    50   -- find out which file name extension shared libraries have
    51   local slib_exts = {}
    52   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
    53     slib_exts[ext] = true
    54   end
    55   local paths = {}
    56   for ext in pairs(slib_exts) do
    57     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
    58   end
    59   for ext in pairs(slib_exts) do
    60     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
    61   end
    62   paths[#paths+1] = package.cpath
    63   package.cpath = table.concat(paths, ";")
    64 end
    66 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
    67 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
    68 -- and models "$WEBMCP_BASE_PATH/model/"
    69 do
    70   local weakkey_mt = { __mode = "k" }
    71   local autoloader_category = setmetatable({}, weakkey_mt)
    72   local autoloader_path     = setmetatable({}, weakkey_mt)
    73   local autoloader_mt       = {}
    74   local function install_autoloader(self, category, path)
    75     autoloader_category[self] = category
    76     autoloader_path[self]     = path
    77     setmetatable(self, autoloader_mt)
    78   end
    79   local function try_exec(filename)
    80     local file = io.open(filename, "r")
    81     if file then
    82       local filedata = file:read("*a")
    83       io.close(file)
    84       local func, errmsg = load(filedata, "=" .. filename)
    85       if func then
    86         func()
    87         return true
    88       else
    89         error(errmsg, 0)
    90       end
    91     else
    92       return false
    93     end
    94   end
    95   local function compose_path_string(base, path, key)
    96     if #path == 0 then
    97       return base .. "/" .. key
    98     else
    99       return base .. table.concat(path, "/") .. "/" .. key
   100     end
   101   end
   102   function autoloader_mt.__index(self, key)
   103     local category, base_path, merge_base_path, file_key
   104     local merge = false
   105     if
   106       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   107       not string.find(key, "^__")
   108     then
   109       category        = "env"
   110       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/"
   111       merge           = true
   112       merge_base_path = WEBMCP_BASE_PATH .. "env/"
   113       file_key        = key
   114     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   115       category        = "model"
   116       base_path       = WEBMCP_BASE_PATH .. "model/"
   117       local first = true
   118       file_key = string.gsub(key, "[A-Z]",
   119         function(c)
   120           if first then
   121             first = false
   122             return string.lower(c)
   123           else
   124             return "_" .. string.lower(c)
   125           end
   126         end
   127       )
   128     else
   129       return
   130     end
   131     local required_category = autoloader_category[self]
   132     if required_category and required_category ~= category then return end
   133     local path = autoloader_path[self]
   134     local path_string = compose_path_string(base_path, path, file_key)
   135     local merge_path_string
   136     if merge then
   137       merge_path_string = compose_path_string(
   138         merge_base_path, path, file_key
   139       )
   140     end
   141     local function try_dir(dirname)
   142       local dir = io.open(dirname)
   143       if dir then
   144         io.close(dir)
   145         local obj = {}
   146         local sub_path = {}
   147         for i = 1, #path do sub_path[i] = path[i] end
   148         sub_path[#path+1] = file_key
   149         install_autoloader(obj, category, sub_path)
   150         rawset(self, key, obj)
   151         try_exec(path_string .. "/__init.lua")
   152         if merge then try_exec(merge_path_string .. "/__init.lua") end
   153         return true
   154       else
   155         return false
   156       end
   157     end
   158     if merge and try_exec(merge_path_string .. ".lua") then
   159     elseif merge and try_dir(merge_path_string .. "/") then
   160     elseif try_exec(path_string .. ".lua") then
   161     elseif try_dir(path_string .. "/") then
   162     else end
   163     return rawget(self, key)
   164   end
   165   install_autoloader(_G, nil, {})
   166   try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
   167   try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
   168 end
   170 -- prohibit (unintended) definition of new global variables
   171 _ENV = setmetatable({}, {
   172   __index = _G,
   173   __newindex = function()
   174     error("Setting of global variable prohibited")
   175   end
   176 })
   178 -- interactive console mode
   179 if WEBMCP_MODE == "interactive" then
   180   trace.disable()  -- avoids memory leakage (TODO: needs general solution for moonbridge?)
   181   local config_name = select(3, ...)
   182   if config_name then
   183     execute.config(config_name)
   184   end
   185   return
   186 end
   188 -- TODO: moonbridge support below this line and in env/request and env/slot
   190 local success, error_info = xpcall(
   191   function()
   193     -- execute configuration file
   194     do
   195       local config_name = request.get_config_name()
   196       if config_name then
   197         execute.config(config_name)
   198       end
   199     end
   201     -- restore slots if coming from http redirect
   202     if cgi.params.tempstore then
   203       trace.restore_slots{}
   204       local blob = tempstore.pop(cgi.params.tempstore)
   205       if blob then slot.restore_all(blob) end
   206     end
   208     local function file_exists(filename)
   209       local file = io.open(filename, "r")
   210       if file then
   211         io.close(file)
   212         return true
   213       else
   214         return false
   215       end
   216     end
   218     if request.is_404() then
   219       request.set_status("404 Not Found")
   220       if request.get_404_route() then
   221         request.forward(request.get_404_route())
   222       else
   223         error("No 404 page set.")
   224       end
   225     elseif request.get_action() then
   226       trace.request{
   227         module = request.get_module(),
   228         action = request.get_action()
   229       }
   230       if
   231         request.get_404_route() and
   232         not file_exists(
   233           encode.action_file_path{
   234             module = request.get_module(),
   235             action = request.get_action()
   236           }
   237         )
   238       then
   239         request.set_status("404 Not Found")
   240         request.forward(request.get_404_route())
   241       else
   242         if cgi.method ~= "POST" then
   243           request.set_status("405 Method Not Allowed")
   244           cgi.add_header("Allow: POST")
   245           error("Tried to invoke an action with a GET request.")
   246         end
   247         local action_status = execute.filtered_action{
   248           module = request.get_module(),
   249           action = request.get_action(),
   250         }
   251         if not request.is_rerouted() then
   252           local routing_mode, routing_module, routing_view
   253           routing_mode   = cgi.params["_webmcp_routing." .. action_status .. ".mode"]
   254           routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"]
   255           routing_view   = cgi.params["_webmcp_routing." .. action_status .. ".view"]
   256           routing_anchor = cgi.params["_webmcp_routing." .. action_status .. ".anchor"]
   257           if not (routing_mode or routing_module or routing_view) then
   258             action_status = "default"
   259             routing_mode   = cgi.params["_webmcp_routing.default.mode"]
   260             routing_module = cgi.params["_webmcp_routing.default.module"]
   261             routing_view   = cgi.params["_webmcp_routing.default.view"]
   262             routing_anchor = cgi.params["_webmcp_routing.default.anchor"]
   263           end
   264           assert(routing_module, "Routing information has no module.")
   265           assert(routing_view,   "Routing information has no view.")
   266           if routing_mode == "redirect" then
   267             local routing_params = {}
   268             for key, value in pairs(cgi.params) do
   269               local status, stripped_key = string.match(
   270                 key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
   271               )
   272               if status == action_status then
   273                 routing_params[stripped_key] = value
   274               end
   275             end
   276             request.redirect{
   277               module = routing_module,
   278               view   = routing_view,
   279               id     = cgi.params["_webmcp_routing." .. action_status .. ".id"],
   280               params = routing_params,
   281               anchor = routing_anchor
   282             }
   283           elseif routing_mode == "forward" then
   284             request.forward{ module = routing_module, view = routing_view }
   285           else
   286             error("Missing or unknown routing mode in request parameters.")
   287           end
   288         end
   289       end
   290     else
   291       -- no action
   292       trace.request{
   293         module = request.get_module(),
   294         view   = request.get_view()
   295       }
   296       if
   297         request.get_404_route() and
   298         not file_exists(
   299           encode.view_file_path{
   300             module = request.get_module(),
   301             view   = request.get_view()
   302           }
   303         )
   304       then
   305         request.set_status("404 Not Found")
   306         request.forward(request.get_404_route())
   307       end
   308     end
   310     if not request.get_redirect_data() then
   311       request.process_forward()
   312       local view = request.get_view()
   313       if string.find(view, "^_") then
   314         error("Tried to call a private view (prefixed with underscore).")
   315       end
   316       execute.filtered_view{
   317         module = request.get_module(),
   318         view   = view,
   319       }
   320     end
   322     -- force error due to missing absolute base URL until its too late to display error message
   323     --if request.get_redirect_data() then
   324     --  request.get_absolute_baseurl()
   325     --end
   327   end,
   329   function(errobj)
   330     return {
   331       errobj = errobj,
   332       stacktrace = string.gsub(
   333         debug.traceback('', 2),
   334         "^\r?\n?stack traceback:\r?\n?", ""
   335       )
   336     }
   337   end
   338 )
   340 if not success then trace.error{} end
   342 -- laufzeitermittlung
   343 trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() }
   345 slot.select('trace', trace.render)  -- render trace information
   347 local redirect_data = request.get_redirect_data()
   349 -- log error and switch to error layout, unless success
   350 if not success then
   351   local errobj     = error_info.errobj
   352   local stacktrace = error_info.stacktrace
   353   if not request.get_status() and not request.get_json_request_slots() then
   354     request.set_status("500 Internal Server Error")
   355   end
   356   slot.set_layout('system_error')
   357   slot.select('system_error', function()
   358     if getmetatable(errobj) == mondelefant.errorobject_metatable then
   359       slot.put(
   360         "<p>Database error of class <b>",
   361         encode.html(errobj.code),
   362         "</b> occured:<br/><b>",
   363         encode.html(errobj.message),
   364         "</b></p>"
   365       )
   366     else
   367       slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
   368     end
   369     slot.put("<p>Stack trace follows:<br/>")
   370     slot.put(encode.html_newlines(encode.html(stacktrace)))
   371     slot.put("</p>")
   372   end)
   373 elseif redirect_data then
   374   local redirect_params = {}
   375   for key, value in pairs(redirect_data.params) do
   376     redirect_params[key] = value
   377   end
   378   local slot_dump = slot.dump_all()
   379   if slot_dump ~= "" then
   380     redirect_params.tempstore = tempstore.save(slot_dump)
   381   end
   382   local json_request_slots = request.get_json_request_slots()
   383   if json_request_slots then
   384     redirect_params["_webmcp_json_slots[]"] = json_request_slots  
   385   end
   386   cgi.redirect(
   387     encode.url{
   388       base   = request.get_absolute_baseurl(),
   389       module = redirect_data.module,
   390       view   = redirect_data.view,
   391       id     = redirect_data.id,
   392       params = redirect_params,
   393       anchor = redirect_data.anchor
   394     }
   395   )
   396   cgi.send_data()
   397 end
   399 if not success or not redirect_data then
   401   local http_status = request.get_status()
   402   if http_status then
   403     cgi.set_status(http_status)
   404   end
   406   local json_request_slots = request.get_json_request_slots()
   407   if json_request_slots then
   408     cgi.set_content_type('application/json')
   409     local data = {}
   410     for idx, slot_ident in ipairs(json_request_slots) do
   411       data[slot_ident] = slot.get_content(slot_ident)
   412     end
   413     cgi.send_data(encode.json(data))
   414   else
   415     cgi.set_content_type(slot.get_content_type())
   416     cgi.send_data(slot.render_layout())
   417   end
   418 end
   420 exit()
