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