webmcp

diff framework/cgi-bin/webmcp.lua @ 0:9fdfb27f8e67

Version 1.0.0
author jbe/bsw
date Sun Oct 25 12:00:00 2009 +0100 (2009-10-25)
parents
children 985024b16520
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/framework/cgi-bin/webmcp.lua	Sun Oct 25 12:00:00 2009 +0100
     1.3 @@ -0,0 +1,449 @@
     1.4 +#!/usr/bin/env lua
     1.5 +
     1.6 +-- include "../lib/" in search path for libraries
     1.7 +do
     1.8 +  package.path = '../lib/?.lua;' .. package.path
     1.9 +  -- find out which file name extension shared libraries have
    1.10 +  local slib_exts = {}
    1.11 +  for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
    1.12 +    slib_exts[ext] = true
    1.13 +  end
    1.14 +  local paths = {}
    1.15 +  for ext in pairs(slib_exts) do
    1.16 +    paths[#paths+1] = "../accelerator/?." .. ext
    1.17 +  end
    1.18 +  for ext in pairs(slib_exts) do
    1.19 +    paths[#paths+1] = "../lib/?." .. ext
    1.20 +  end
    1.21 +  paths[#paths+1] = package.cpath
    1.22 +  package.cpath = table.concat(paths, ";")
    1.23 +end
    1.24 +
    1.25 +-- load os extensions for lua
    1.26 +-- (should happen as soon as possible due to run time measurement)
    1.27 +require 'extos'
    1.28 +
    1.29 +-- load nihil library
    1.30 +require 'nihil'
    1.31 +
    1.32 +-- load random generator library
    1.33 +require 'multirand'
    1.34 +
    1.35 +-- load rocketcgi library and map it to cgi
    1.36 +do
    1.37 +  local option = os.getenv("WEBMCP_INTERACTIVE")
    1.38 +  if option and option ~= "" and option ~= "0" then
    1.39 +    cgi = nil
    1.40 +  else
    1.41 +    require 'rocketcgi'
    1.42 +    cgi = rocketcgi
    1.43 +  end
    1.44 +end
    1.45 +
    1.46 +-- load database access library with object relational mapper
    1.47 +require 'mondelefant'
    1.48 +mondelefant.connection_prototype.error_objects = true
    1.49 +
    1.50 +-- load type system "atom"
    1.51 +require 'atom'
    1.52 +
    1.53 +-- load mondelefant atom connector
    1.54 +require 'mondelefant_atom_connector'
    1.55 +
    1.56 +--[[--
    1.57 +cloned_table =  -- newly generated table
    1.58 +table.new(
    1.59 +  table_or_nil  -- keys of a given table will be copied to the new table
    1.60 +)
    1.61 +
    1.62 +If a table is given, then a cloned table is returned.
    1.63 +If nil is given, then a new empty table is returned.
    1.64 +
    1.65 +--]]--
    1.66 +function table.new(tbl)
    1.67 +  new_tbl = {}
    1.68 +  if tbl then
    1.69 +    for key, value in pairs(tbl) do
    1.70 +      new_tbl[key] = value
    1.71 +    end
    1.72 +  end
    1.73 +  return new_tbl
    1.74 +end
    1.75 +--//--
    1.76 +
    1.77 +--[[--
    1.78 +at_exit(
    1.79 +  func  -- function to be called before the process is ending
    1.80 +)
    1.81 +
    1.82 +Registers a function to be called before the CGI process is exiting.
    1.83 +--]]--
    1.84 +do
    1.85 +  local exit_handlers = {}
    1.86 +  function at_exit(func)
    1.87 +    table.insert(exit_handlers, func)
    1.88 +  end
    1.89 +  function exit(code)
    1.90 +    for i = #exit_handlers, 1, -1 do
    1.91 +      exit_handlers[i]()
    1.92 +    end
    1.93 +    os.exit(code)
    1.94 +  end
    1.95 +end
    1.96 +--//--
    1.97 +
    1.98 +--[[--
    1.99 +app  -- table to store an application state
   1.100 +
   1.101 +'app' is a global table for storing any application state data
   1.102 +--]]--
   1.103 +app = {}
   1.104 +--//--
   1.105 +
   1.106 +--[[--
   1.107 +config  -- table to store application configuration
   1.108 +
   1.109 +'config' is a global table, which can be modified by a config file of an application to modify the behaviour of that application.
   1.110 +--]]--
   1.111 +config = {}
   1.112 +--//--
   1.113 +
   1.114 +-- autoloader system for WebMCP environment "../env/",
   1.115 +-- application environment extensions "$WEBMCP_APP_BASE/env/"
   1.116 +-- and models "$WEBMCP_APP_BASE/model/"
   1.117 +do
   1.118 +  local app_base = os.getenv("WEBMCP_APP_BASEPATH")
   1.119 +  if not app_base then
   1.120 +    error(
   1.121 +      "Failed to initialize autoloader " ..
   1.122 +      "due to unset WEBMCP_APP_BASEPATH environment variable."
   1.123 +    )
   1.124 +  end
   1.125 +  local weakkey_mt = { __mode = "k" }
   1.126 +  local autoloader_category = setmetatable({}, weakkey_mt)
   1.127 +  local autoloader_path     = setmetatable({}, weakkey_mt)
   1.128 +  local autoloader_mt       = {}
   1.129 +  local function install_autoloader(self, category, path)
   1.130 +    autoloader_category[self] = category
   1.131 +    autoloader_path[self]     = path
   1.132 +    setmetatable(self, autoloader_mt)
   1.133 +  end
   1.134 +  local function try_exec(filename)
   1.135 +    local file = io.open(filename, "r")
   1.136 +    if file then
   1.137 +      local filedata = file:read("*a")
   1.138 +      io.close(file)
   1.139 +      local func, errmsg = loadstring(filedata, "=" .. filename)
   1.140 +      if func then
   1.141 +        func()
   1.142 +        return true
   1.143 +      else
   1.144 +        error(errmsg, 0)
   1.145 +      end
   1.146 +    else
   1.147 +      return false
   1.148 +    end
   1.149 +  end
   1.150 +  local function compose_path_string(base, path, key)
   1.151 +    return string.gsub(
   1.152 +      base .. table.concat(path, "/") .. "/" .. key, "/+", "/"
   1.153 +    )
   1.154 +  end
   1.155 +  function autoloader_mt.__index(self, key)
   1.156 +    local category, base_path, merge_base_path, file_key
   1.157 +    local merge = false
   1.158 +    if
   1.159 +      string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   1.160 +      not string.find(key, "^__")
   1.161 +    then
   1.162 +      category        = "env"
   1.163 +      base_path       = "../env/"
   1.164 +      merge           = true
   1.165 +      merge_base_path = app_base .. "/env/"
   1.166 +      file_key        = key
   1.167 +    elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   1.168 +      category        = "model"
   1.169 +      base_path       = app_base .. "/model/"
   1.170 +      local first = true
   1.171 +      file_key = string.gsub(key, "[A-Z]",
   1.172 +        function(c)
   1.173 +          if first then
   1.174 +            first = false
   1.175 +            return string.lower(c)
   1.176 +          else
   1.177 +            return "_" .. string.lower(c)
   1.178 +          end
   1.179 +        end
   1.180 +      )
   1.181 +    else
   1.182 +      return
   1.183 +    end
   1.184 +    local required_category = autoloader_category[self]
   1.185 +    if required_category and required_category ~= category then return end
   1.186 +    local path = autoloader_path[self]
   1.187 +    local path_string = compose_path_string(base_path, path, file_key)
   1.188 +    local merge_path_string
   1.189 +    if merge then
   1.190 +      merge_path_string = compose_path_string(
   1.191 +        merge_base_path, path, file_key
   1.192 +      )
   1.193 +    end
   1.194 +    local function try_dir(dirname)
   1.195 +      local dir = io.open(dirname)
   1.196 +      if dir then
   1.197 +        io.close(dir)
   1.198 +        local obj = {}
   1.199 +        local sub_path = {}
   1.200 +        for i, v in ipairs(path) do sub_path[i] = v end
   1.201 +        table.insert(sub_path, file_key)
   1.202 +        install_autoloader(obj, category, sub_path)
   1.203 +        rawset(self, key, obj)
   1.204 +        try_exec(path_string .. "/__init.lua")
   1.205 +        if merge then try_exec(merge_path_string .. "/__init.lua") end
   1.206 +        return true
   1.207 +      else
   1.208 +        return false
   1.209 +      end
   1.210 +    end
   1.211 +    if merge and try_exec(merge_path_string .. ".lua") then
   1.212 +    elseif merge and try_dir(merge_path_string .. "/") then
   1.213 +    elseif try_exec(path_string .. ".lua") then
   1.214 +    elseif try_dir(path_string .. "/") then
   1.215 +    else end
   1.216 +    return rawget(self, key)
   1.217 +  end
   1.218 +  install_autoloader(_G, nil, {})
   1.219 +  try_exec("../env/__init.lua")
   1.220 +end
   1.221 +
   1.222 +-- interactive console mode
   1.223 +if not cgi then
   1.224 +  local config_name = request.get_config_name()
   1.225 +  if config_name then
   1.226 +    execute.config(config_name)
   1.227 +  end
   1.228 +  return
   1.229 +end
   1.230 +
   1.231 +local success, error_info = xpcall(
   1.232 +  function()
   1.233 +
   1.234 +    -- execute configuration file
   1.235 +    do
   1.236 +      local config_name = request.get_config_name()
   1.237 +      if config_name then
   1.238 +        execute.config(config_name)
   1.239 +      end
   1.240 +    end
   1.241 +
   1.242 +    -- restore slots if coming from http redirect
   1.243 +    if cgi.params.tempstore then
   1.244 +      trace.restore_slots{}
   1.245 +      local blob = tempstore.pop(cgi.params.tempstore)
   1.246 +      if blob then slot.restore_all(blob) end
   1.247 +    end
   1.248 +
   1.249 +    local function file_exists(filename)
   1.250 +      local file = io.open(filename, "r")
   1.251 +      if file then
   1.252 +        io.close(file)
   1.253 +        return true
   1.254 +      else
   1.255 +        return false
   1.256 +      end
   1.257 +    end
   1.258 +
   1.259 +    if cgi.params["_webmcp_404"] then
   1.260 +      request.force_absolute_baseurl()
   1.261 +      request.set_status("404 Not Found")
   1.262 +      if request.get_404_route() then
   1.263 +        request.forward(request.get_404_route())
   1.264 +      else
   1.265 +        error("No 404 page set.")
   1.266 +      end
   1.267 +    elseif request.get_action() then
   1.268 +      trace.request{
   1.269 +        module = request.get_module(),
   1.270 +        action = request.get_action()
   1.271 +      }
   1.272 +      if
   1.273 +        request.get_404_route() and
   1.274 +        not file_exists(
   1.275 +          encode.action_file_path{
   1.276 +            module = request.get_module(),
   1.277 +            action = request.get_action()
   1.278 +          }
   1.279 +        )
   1.280 +      then
   1.281 +        request.set_status("404 Not Found")
   1.282 +        request.forward(request.get_404_route())
   1.283 +      else
   1.284 +        if cgi.method ~= "POST" then
   1.285 +          request.set_status("405 Method Not Allowed")
   1.286 +          cgi.add_header("Allow: POST")
   1.287 +          error("Tried to invoke an action with a GET request.")
   1.288 +        end
   1.289 +        local action_status = execute.filtered_action{
   1.290 +          module = request.get_module(),
   1.291 +          action = request.get_action(),
   1.292 +        }
   1.293 +        if not request.is_rerouted() then
   1.294 +          local routing_mode, routing_module, routing_view
   1.295 +          routing_mode   = cgi.params["_webmcp_routing." .. action_status .. ".mode"]
   1.296 +          routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"]
   1.297 +          routing_view   = cgi.params["_webmcp_routing." .. action_status .. ".view"]
   1.298 +          if not (routing_mode or routing_module or routing_view) then
   1.299 +            action_status = "default"
   1.300 +            routing_mode   = cgi.params["_webmcp_routing.default.mode"]
   1.301 +            routing_module = cgi.params["_webmcp_routing.default.module"]
   1.302 +            routing_view   = cgi.params["_webmcp_routing.default.view"]
   1.303 +          end
   1.304 +          assert(routing_module, "Routing information has no module.")
   1.305 +          assert(routing_view,   "Routing information has no view.")
   1.306 +          if routing_mode == "redirect" then
   1.307 +            local routing_params = {}
   1.308 +            for key, value in pairs(cgi.params) do
   1.309 +              local status, stripped_key = string.match(
   1.310 +                key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
   1.311 +              )
   1.312 +              if status == action_status then
   1.313 +                routing_params[stripped_key] = value
   1.314 +              end
   1.315 +            end
   1.316 +            request.redirect{
   1.317 +              module = routing_module,
   1.318 +              view   = routing_view,
   1.319 +              id     = cgi.params["_webmcp_routing." .. action_status .. ".id"],
   1.320 +              params = routing_params
   1.321 +            }
   1.322 +          elseif routing_mode == "forward" then
   1.323 +            request.forward{ module = routing_module, view = routing_view }
   1.324 +          else
   1.325 +            error("Missing or unknown routing mode in request parameters.")
   1.326 +          end
   1.327 +        end
   1.328 +      end
   1.329 +    else
   1.330 +      -- no action
   1.331 +      trace.request{
   1.332 +        module = request.get_module(),
   1.333 +        view   = request.get_view()
   1.334 +      }
   1.335 +      if
   1.336 +        request.get_404_route() and
   1.337 +        not file_exists(
   1.338 +          encode.view_file_path{
   1.339 +            module = request.get_module(),
   1.340 +            view   = request.get_view()
   1.341 +          }
   1.342 +        )
   1.343 +      then
   1.344 +        request.set_status("404 Not Found")
   1.345 +        request.forward(request.get_404_route())
   1.346 +      end
   1.347 +    end
   1.348 +
   1.349 +    if not request.get_redirect_data() then
   1.350 +      request.process_forward()
   1.351 +      execute.filtered_view{
   1.352 +        module = request.get_module(),
   1.353 +        view = request.get_view(),
   1.354 +      }
   1.355 +    end
   1.356 +
   1.357 +    -- force error due to missing absolute base URL until its too late to display error message
   1.358 +    --if request.get_redirect_data() then
   1.359 +    --  request.get_absolute_baseurl()
   1.360 +    --end
   1.361 +
   1.362 +  end,
   1.363 +
   1.364 +  function(errobj)
   1.365 +    return {
   1.366 +      errobj = errobj,
   1.367 +      stacktrace = string.gsub(
   1.368 +        debug.traceback('', 2),
   1.369 +        "^\r?\n?stack traceback:\r?\n?", ""
   1.370 +      )
   1.371 +    }
   1.372 +  end
   1.373 +)
   1.374 +
   1.375 +if not success then trace.error{} end
   1.376 +
   1.377 +-- laufzeitermittlung
   1.378 +trace.exectime{ real = os.monotonic_hires_time(), cpu = os.clock() }
   1.379 +
   1.380 +slot.select('trace', trace.render)  -- render trace information
   1.381 +
   1.382 +local redirect_data = request.get_redirect_data()
   1.383 +
   1.384 +-- log error and switch to error layout, unless success
   1.385 +if not success then
   1.386 +  local errobj     = error_info.errobj
   1.387 +  local stacktrace = error_info.stacktrace
   1.388 +  if not request.get_status() then
   1.389 +    request.set_status("500 Internal Server Error")
   1.390 +  end
   1.391 +  slot.set_layout('system_error')
   1.392 +  slot.select('system_error', function()
   1.393 +    if getmetatable(errobj) == mondelefant.errorobject_metatable then
   1.394 +      slot.put(
   1.395 +        "<p>Database error of class <b>",
   1.396 +        encode.html(errobj.code),
   1.397 +        "</b> occured:<br/><b>",
   1.398 +        encode.html(errobj.message),
   1.399 +        "</b></p>"
   1.400 +      )
   1.401 +    else
   1.402 +      slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
   1.403 +    end
   1.404 +    slot.put("<p>Stack trace follows:<br/>")
   1.405 +    slot.put(encode.html_newlines(encode.html(stacktrace)))
   1.406 +    slot.put("</p>")
   1.407 +  end)
   1.408 +elseif redirect_data then
   1.409 +  local redirect_params = {}
   1.410 +  for key, value in pairs(redirect_data.params) do
   1.411 +    redirect_params[key] = value
   1.412 +  end
   1.413 +  local slot_dump = slot.dump_all()
   1.414 +  if slot_dump ~= "" then
   1.415 +    redirect_params.tempstore = tempstore.save(slot_dump)
   1.416 +  end
   1.417 +  cgi.redirect(
   1.418 +    encode.url{
   1.419 +      base   = request.get_absolute_baseurl(),
   1.420 +      module = redirect_data.module,
   1.421 +      view   = redirect_data.view,
   1.422 +      id     = redirect_data.id,
   1.423 +      params = redirect_params
   1.424 +    }
   1.425 +  )
   1.426 +  cgi.send_data()
   1.427 +end
   1.428 +
   1.429 +if not success or not redirect_data then
   1.430 +
   1.431 +  local http_status = request.get_status()
   1.432 +  if http_status then
   1.433 +    cgi.set_status(http_status)
   1.434 +  end
   1.435 +
   1.436 +  -- ajax
   1.437 +  if request.is_ajax() then
   1.438 +    cgi.set_content_type('text/html')
   1.439 +    for i, slot_ident in ipairs{'main', 'actions', 'title', 'topnav', 'sidenav', 'debug', 'notice', 'warning', 'error'} do
   1.440 +      local html = slot.get_content(slot_ident)
   1.441 +      if html then
   1.442 +        cgi.send_data("document.getElementById('" .. slot_ident .. "').innerHTML=" .. encode.json(html or '&nbsp;') .. ";")
   1.443 +      end
   1.444 +    end
   1.445 +  -- oder ganz herkoemmlich
   1.446 +  else
   1.447 +    cgi.set_content_type(slot.get_content_type())
   1.448 +    cgi.send_data(slot.render_layout())
   1.449 +  end
   1.450 +end
   1.451 +
   1.452 +exit()

Impressum / About Us