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