webmcp
view framework/cgi-bin/webmcp.lua @ 68:ebdc20b7048b
Lua 5.2 compatibility:
Make function load accepting strings as first argument
Make function load accepting strings as first argument
| author | jbe | 
|---|---|
| date | Tue Apr 17 16:26:32 2012 +0200 (2012-04-17) | 
| parents | a54cc7dcabf1 | 
| children | 343e121178d1 | 
 line source
     1 #!/usr/bin/env lua
     3 _WEBMCP_VERSION = "1.1.4"
     5 -- Lua 5.1 compatibility
     6 if not table.unpack then
     7   table.unpack = unpack
     8 end
     9 do
    10   local old_load = load
    11   function load(ld, ...)
    12     if type(ld) == string then
    13       local done = false
    14       local func = function()
    15         if not done then
    16           done = true
    17           return ld
    18         end
    19       end
    20       return old_load(func, ...)
    21     else
    22       return old_load(ld, ...)
    23     end
    24   end
    25 end
    27 -- include "../lib/" in search path for libraries
    28 if not WEBMCP_PATH then
    29   WEBMCP_PATH = "../"
    30 end
    32 do
    33   package.path = WEBMCP_PATH .. 'lib/?.lua;' .. package.path
    34   -- find out which file name extension shared libraries have
    35   local slib_exts = {}
    36   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
    37     slib_exts[ext] = true
    38   end
    39   local paths = {}
    40   for ext in pairs(slib_exts) do
    41     paths[#paths+1] = WEBMCP_PATH .. "accelerator/?." .. ext
    42   end
    43   for ext in pairs(slib_exts) do
    44     paths[#paths+1] = WEBMCP_PATH .. "lib/?." .. ext
    45   end
    46   paths[#paths+1] = package.cpath
    47   package.cpath = table.concat(paths, ";")
    48 end
    50 -- load os extensions for lua
    51 -- (should happen as soon as possible due to run time measurement)
    52 extos = require 'extos'
    54 -- load nihil library
    55 nihil = require 'nihil'
    57 -- load random generator library
    58 multirand = require 'multirand'
    60 -- load rocketcgi library and map it to cgi
    61 do
    62   local option = os.getenv("WEBMCP_INTERACTIVE")
    63   if option and option ~= "" and option ~= "0" then
    64     cgi = nil
    65   else
    66     rocketcgi = require 'rocketcgi'
    67     cgi = rocketcgi
    68   end
    69 end
    71 -- load database access library with object relational mapper
    72 mondelefant = require 'mondelefant'
    73 mondelefant.connection_prototype.error_objects = true
    75 -- load type system "atom"
    76 atom = require 'atom'
    78 -- load mondelefant atom connector
    79 require 'mondelefant_atom_connector'
    81 --[[--
    82 cloned_table =  -- newly generated table
    83 table.new(
    84   table_or_nil  -- keys of a given table will be copied to the new table
    85 )
    87 If a table is given, then a cloned table is returned.
    88 If nil is given, then a new empty table is returned.
    90 --]]--
    91 function table.new(tbl)
    92   new_tbl = {}
    93   if tbl then
    94     for key, value in pairs(tbl) do
    95       new_tbl[key] = value
    96     end
    97   end
    98   return new_tbl
    99 end
   100 --//--
   102 --[[--
   103 at_exit(
   104   func  -- function to be called before the process is ending
   105 )
   107 Registers a function to be called before the CGI process is exiting.
   108 --]]--
   109 do
   110   local exit_handlers = {}
   111   function at_exit(func)
   112     table.insert(exit_handlers, func)
   113   end
   114   function exit(code)
   115     for i = #exit_handlers, 1, -1 do
   116       exit_handlers[i]()
   117     end
   118     os.exit(code)
   119   end
   120 end
   121 --//--
   123 --[[--
   124 app  -- table to store an application state
   126 'app' is a global table for storing any application state data
   127 --]]--
   128 app = {}
   129 --//--
   131 --[[--
   132 config  -- table to store application configuration
   134 'config' is a global table, which can be modified by a config file of an application to modify the behaviour of that application.
   135 --]]--
   136 config = {}
   137 --//--
   139 -- autoloader system for WebMCP environment "../env/",
   140 -- application environment extensions "$WEBMCP_APP_BASE/env/"
   141 -- and models "$WEBMCP_APP_BASE/model/"
   142 do
   143   local app_base = os.getenv("WEBMCP_APP_BASEPATH")
   144   if not app_base then
   145     error(
   146       "Failed to initialize autoloader " ..
   147       "due to unset WEBMCP_APP_BASEPATH environment variable."
   148     )
   149   end
   150   local weakkey_mt = { __mode = "k" }
   151   local autoloader_category = setmetatable({}, weakkey_mt)
   152   local autoloader_path     = setmetatable({}, weakkey_mt)
   153   local autoloader_mt       = {}
   154   local function install_autoloader(self, category, path)
   155     autoloader_category[self] = category
   156     autoloader_path[self]     = path
   157     setmetatable(self, autoloader_mt)
   158   end
   159   local function try_exec(filename)
   160     local file = io.open(filename, "r")
   161     if file then
   162       local filedata = file:read("*a")
   163       io.close(file)
   164       local func, errmsg = load(filedata, "=" .. filename)
   165       if func then
   166         func()
   167         return true
   168       else
   169         error(errmsg, 0)
   170       end
   171     else
   172       return false
   173     end
   174   end
   175   local function compose_path_string(base, path, key)
   176     return string.gsub(
   177       base .. table.concat(path, "/") .. "/" .. key, "/+", "/"
   178     )
   179   end
   180   function autoloader_mt.__index(self, key)
   181     local category, base_path, merge_base_path, file_key
   182     local merge = false
   183     if
   184       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   185       not string.find(key, "^__")
   186     then
   187       category        = "env"
   188       base_path       = WEBMCP_PATH .. "/env/"
   189       merge           = true
   190       merge_base_path = app_base .. "/env/"
   191       file_key        = key
   192     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   193       category        = "model"
   194       base_path       = app_base .. "/model/"
   195       local first = true
   196       file_key = string.gsub(key, "[A-Z]",
   197         function(c)
   198           if first then
   199             first = false
   200             return string.lower(c)
   201           else
   202             return "_" .. string.lower(c)
   203           end
   204         end
   205       )
   206     else
   207       return
   208     end
   209     local required_category = autoloader_category[self]
   210     if required_category and required_category ~= category then return end
   211     local path = autoloader_path[self]
   212     local path_string = compose_path_string(base_path, path, file_key)
   213     local merge_path_string
   214     if merge then
   215       merge_path_string = compose_path_string(
   216         merge_base_path, path, file_key
   217       )
   218     end
   219     local function try_dir(dirname)
   220       local dir = io.open(dirname)
   221       if dir then
   222         io.close(dir)
   223         local obj = {}
   224         local sub_path = {}
   225         for i, v in ipairs(path) do sub_path[i] = v end
   226         table.insert(sub_path, file_key)
   227         install_autoloader(obj, category, sub_path)
   228         rawset(self, key, obj)
   229         try_exec(path_string .. "/__init.lua")
   230         if merge then try_exec(merge_path_string .. "/__init.lua") end
   231         return true
   232       else
   233         return false
   234       end
   235     end
   236     if merge and try_exec(merge_path_string .. ".lua") then
   237     elseif merge and try_dir(merge_path_string .. "/") then
   238     elseif try_exec(path_string .. ".lua") then
   239     elseif try_dir(path_string .. "/") then
   240     else end
   241     return rawget(self, key)
   242   end
   243   install_autoloader(_G, nil, {})
   244   try_exec(WEBMCP_PATH .. "env/__init.lua")
   245 end
   247 -- interactive console mode
   248 if not cgi then
   249   local config_name = request.get_config_name()
   250   if config_name then
   251     execute.config(config_name)
   252   end
   253   return
   254 end
   256 local success, error_info = xpcall(
   257   function()
   259     -- execute configuration file
   260     do
   261       local config_name = request.get_config_name()
   262       if config_name then
   263         execute.config(config_name)
   264       end
   265     end
   267     -- restore slots if coming from http redirect
   268     if cgi.params.tempstore then
   269       trace.restore_slots{}
   270       local blob = tempstore.pop(cgi.params.tempstore)
   271       if blob then slot.restore_all(blob) end
   272     end
   274     local function file_exists(filename)
   275       local file = io.open(filename, "r")
   276       if file then
   277         io.close(file)
   278         return true
   279       else
   280         return false
   281       end
   282     end
   284     if cgi.params["_webmcp_404"] then
   285       request.force_absolute_baseurl()
   286       request.set_status("404 Not Found")
   287       if request.get_404_route() then
   288         request.forward(request.get_404_route())
   289       else
   290         error("No 404 page set.")
   291       end
   292     elseif request.get_action() then
   293       trace.request{
   294         module = request.get_module(),
   295         action = request.get_action()
   296       }
   297       if
   298         request.get_404_route() and
   299         not file_exists(
   300           encode.action_file_path{
   301             module = request.get_module(),
   302             action = request.get_action()
   303           }
   304         )
   305       then
   306         request.set_status("404 Not Found")
   307         request.forward(request.get_404_route())
   308       else
   309         if cgi.method ~= "POST" then
   310           request.set_status("405 Method Not Allowed")
   311           cgi.add_header("Allow: POST")
   312           error("Tried to invoke an action with a GET request.")
   313         end
   314         local action_status = execute.filtered_action{
   315           module = request.get_module(),
   316           action = request.get_action(),
   317         }
   318         if not request.is_rerouted() then
   319           local routing_mode, routing_module, routing_view
   320           routing_mode   = cgi.params["_webmcp_routing." .. action_status .. ".mode"]
   321           routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"]
   322           routing_view   = cgi.params["_webmcp_routing." .. action_status .. ".view"]
   323           if not (routing_mode or routing_module or routing_view) then
   324             action_status = "default"
   325             routing_mode   = cgi.params["_webmcp_routing.default.mode"]
   326             routing_module = cgi.params["_webmcp_routing.default.module"]
   327             routing_view   = cgi.params["_webmcp_routing.default.view"]
   328           end
   329           assert(routing_module, "Routing information has no module.")
   330           assert(routing_view,   "Routing information has no view.")
   331           if routing_mode == "redirect" then
   332             local routing_params = {}
   333             for key, value in pairs(cgi.params) do
   334               local status, stripped_key = string.match(
   335                 key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
   336               )
   337               if status == action_status then
   338                 routing_params[stripped_key] = value
   339               end
   340             end
   341             request.redirect{
   342               module = routing_module,
   343               view   = routing_view,
   344               id     = cgi.params["_webmcp_routing." .. action_status .. ".id"],
   345               params = routing_params
   346             }
   347           elseif routing_mode == "forward" then
   348             request.forward{ module = routing_module, view = routing_view }
   349           else
   350             error("Missing or unknown routing mode in request parameters.")
   351           end
   352         end
   353       end
   354     else
   355       -- no action
   356       trace.request{
   357         module = request.get_module(),
   358         view   = request.get_view()
   359       }
   360       if
   361         request.get_404_route() and
   362         not file_exists(
   363           encode.view_file_path{
   364             module = request.get_module(),
   365             view   = request.get_view()
   366           }
   367         )
   368       then
   369         request.set_status("404 Not Found")
   370         request.forward(request.get_404_route())
   371       end
   372     end
   374     if not request.get_redirect_data() then
   375       request.process_forward()
   376       local view = request.get_view()
   377       if string.find(view, "^_") then
   378         error("Tried to call a private view (prefixed with underscore).")
   379       end
   380       execute.filtered_view{
   381         module = request.get_module(),
   382         view   = view,
   383       }
   384     end
   386     -- force error due to missing absolute base URL until its too late to display error message
   387     --if request.get_redirect_data() then
   388     --  request.get_absolute_baseurl()
   389     --end
   391   end,
   393   function(errobj)
   394     return {
   395       errobj = errobj,
   396       stacktrace = string.gsub(
   397         debug.traceback('', 2),
   398         "^\r?\n?stack traceback:\r?\n?", ""
   399       )
   400     }
   401   end
   402 )
   404 if not success then trace.error{} end
   406 -- laufzeitermittlung
   407 trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() }
   409 slot.select('trace', trace.render)  -- render trace information
   411 local redirect_data = request.get_redirect_data()
   413 -- log error and switch to error layout, unless success
   414 if not success then
   415   local errobj     = error_info.errobj
   416   local stacktrace = error_info.stacktrace
   417   if not request.get_status() and not request.get_json_request_slots() then
   418     request.set_status("500 Internal Server Error")
   419   end
   420   slot.set_layout('system_error')
   421   slot.select('system_error', function()
   422     if getmetatable(errobj) == mondelefant.errorobject_metatable then
   423       slot.put(
   424         "<p>Database error of class <b>",
   425         encode.html(errobj.code),
   426         "</b> occured:<br/><b>",
   427         encode.html(errobj.message),
   428         "</b></p>"
   429       )
   430     else
   431       slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
   432     end
   433     slot.put("<p>Stack trace follows:<br/>")
   434     slot.put(encode.html_newlines(encode.html(stacktrace)))
   435     slot.put("</p>")
   436   end)
   437 elseif redirect_data then
   438   local redirect_params = {}
   439   for key, value in pairs(redirect_data.params) do
   440     redirect_params[key] = value
   441   end
   442   local slot_dump = slot.dump_all()
   443   if slot_dump ~= "" then
   444     redirect_params.tempstore = tempstore.save(slot_dump)
   445   end
   446   local json_request_slots = request.get_json_request_slots()
   447   if json_request_slots then
   448     redirect_params["_webmcp_json_slots[]"] = json_request_slots  
   449   end
   450   cgi.redirect(
   451     encode.url{
   452       base   = request.get_absolute_baseurl(),
   453       module = redirect_data.module,
   454       view   = redirect_data.view,
   455       id     = redirect_data.id,
   456       params = redirect_params
   457     }
   458   )
   459   cgi.send_data()
   460 end
   462 if not success or not redirect_data then
   464   local http_status = request.get_status()
   465   if http_status then
   466     cgi.set_status(http_status)
   467   end
   469   local json_request_slots = request.get_json_request_slots()
   470   if json_request_slots then
   471     cgi.set_content_type('application/json')
   472     local data = {}
   473     for idx, slot_ident in ipairs(json_request_slots) do
   474       data[slot_ident] = slot.get_content(slot_ident)
   475     end
   476     cgi.send_data(encode.json(data))
   477   else
   478     cgi.set_content_type(slot.get_content_type())
   479     cgi.send_data(slot.render_layout())
   480   end
   481 end
   483 exit()
