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()