# HG changeset patch # User jbe # Date 1426372787 -3600 # Node ID 9e4be058959d2bd87a84b196f3d264b11a820354 # Parent 2169a62e12f5a1b7d5b58ccb0f9bddab020b6291 New functions request.add_initializer(...) and request.add_variable(...) to allow per-request initialization; Merged request.process() with request.handler(...) diff -r 2169a62e12f5 -r 9e4be058959d framework/env/request/__init.lua --- a/framework/env/request/__init.lua Mon Mar 02 01:15:34 2015 +0100 +++ b/framework/env/request/__init.lua Sat Mar 14 23:39:47 2015 +0100 @@ -1,16 +1,22 @@ -request._http_request = nil +request._initializers = {} +request._in_progress = false + +-- initialize once +request._absolute_baseurl = nil request._http_options = {} -request._response_headers = {} -request._status = nil -request._forward = nil -request._forward_processed = false -request._redirect = nil -request._absolute_baseurl = nil -request._404_route = nil -request._force_absolute_baseurl = false -request._perm_params = {} -request._csrf_secret = nil +-- initialize once and re-initialize per request +request.add_variable(request, "_response_headers", {}) +request.add_variable(request, "_force_absolute_baseurl", false) +request.add_variable(request, "_perm_params", {}) +request.add_variable(request, "_404_route", nil) -request._params = {} - +-- initialize per request +request.add_initializer(function() + request._http_request = nil + request._status = nil + request._forward = nil + request._forward_processed = false + request._redirect = nil + request._csrf_secret = nil +end) diff -r 2169a62e12f5 -r 9e4be058959d framework/env/request/add_initializer.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/env/request/add_initializer.lua Sat Mar 14 23:39:47 2015 +0100 @@ -0,0 +1,16 @@ +--[[-- +request.add_initializer( + func -- function to be called on every request +) + +Registers a function to be called on every request. This mechanism can be used for __init.lua files in the environment to perform a per-request initialization. See env/request/__init.lua for an example. + +--]]-- + +function request.add_initializer(func) + local initializers = request._initializers + initializers[#initializers+1] = func + if request._in_progress then + func(true) + end +end diff -r 2169a62e12f5 -r 9e4be058959d framework/env/request/add_variable.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/env/request/add_variable.lua Sat Mar 14 23:39:47 2015 +0100 @@ -0,0 +1,22 @@ +--[[-- +request.add_variable( + tbl, -- table where the variable is stored + key, -- name of variable (key within the table) + value -- optional value for initialization +) + +Marks a field of a table to be re-initialized for every request. If this variable (i.e. the field of the table) is modified before the first requst is being handled (e.g. during configuration or pre-/post-fork initializers), then the modified value will be used for re-initialization on every request. See env/request/__init.lua for an example. + +--]]-- + +function request.add_variable(tbl, key, value) + local initialized_value + tbl[key] = value + request.add_initializer(function(first) + if first then + initialized_value = tbl[key] + else + tbl[key] = initialized_value + end + end) +end diff -r 2169a62e12f5 -r 9e4be058959d framework/env/request/handler.lua --- a/framework/env/request/handler.lua Mon Mar 02 01:15:34 2015 +0100 +++ b/framework/env/request/handler.lua Sat Mar 14 23:39:47 2015 +0100 @@ -3,11 +3,30 @@ request -- HTTP request object ) -Called by mcp.lua to process an HTTP request. Performs some initializations, then calls request.router(). +Called by mcp.lua to process an HTTP request. Performs some initializations, calls request.router(), and handles the request. --]]-- +local function file_exists(filename) + local file = io.open(filename, "r") + if file then + io.close(file) + return true + else + return false + end +end + +-- TODO: function incomplete yet function request.handler(http_request) + do + local first = not request._in_progress + request._in_progress = true -- NOTE: must be set to true before initializer functions are called + for i, func in ipairs(request._initializers) do + func(first) + end + end + request._http_request = http_request local path = http_request.path if path then @@ -20,7 +39,210 @@ request._relative_baseurl = nil end request._route = request.router() or {} - request.process() + + local success, error_info = xpcall( + function() + + if request._route.static then + local f = assert(io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r")) + local d = f:read("*a") + f:close() + slot.put_into("data", d) + slot.set_layout(nil, "application/octet-stream") -- TODO + return + end + + -- restore slots if coming from http redirect + local tempstore_value = http_request.get_params["_tempstore"] + if tempstore_value then + trace.restore_slots{} + local blob = tempstore.pop(tempstore_value) + if blob then slot.restore_all(blob) end + end + + if request.get_action() then + trace.request{ + module = request.get_module(), + action = request.get_action() + } + if + request.get_404_route() and + not file_exists( + encode.action_file_path{ + module = request.get_module(), + action = request.get_action() + } + ) + then + request.set_status("404 Not Found") + request.forward(request.get_404_route()) + else + if http_request.method ~= "POST" then + request.set_status("405 Method Not Allowed") + request.add_header("Allow", "POST") + error("Tried to invoke an action with a GET request.") + end + local action_status = execute.filtered_action{ + module = request.get_module(), + action = request.get_action(), + } + if not request.is_rerouted() then + local routing_mode, routing_module, routing_view, routing_anchor + routing_mode = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] + routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"] + routing_view = http_request.post_params["_webmcp_routing." .. action_status .. ".view"] + routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] + if not (routing_mode or routing_module or routing_view) then + action_status = "default" + routing_mode = http_request.post_params["_webmcp_routing.default.mode"] + routing_module = http_request.post_params["_webmcp_routing.default.module"] + routing_view = http_request.post_params["_webmcp_routing.default.view"] + routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"] + end + assert(routing_module, "Routing information has no module.") + assert(routing_view, "Routing information has no view.") + if routing_mode == "redirect" then + local routing_params = {} + for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do + local status, stripped_key = string.match( + key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" + ) + if status == action_status then + routing_params[stripped_key] = value + end + end + request.redirect{ + module = routing_module, + view = routing_view, + id = http_request.post_params["_webmcp_routing." .. action_status .. ".id"], + params = routing_params, + anchor = routing_anchor + } + elseif routing_mode == "forward" then + request.forward{ module = routing_module, view = routing_view } + else + error("Missing or unknown routing mode in request parameters.") + end + end + end + else + -- no action + trace.request{ + module = request.get_module(), + view = request.get_view() + } + if + request.get_404_route() and + not file_exists( + encode.view_file_path{ + module = request.get_module(), + view = request.get_view() + } + ) + then + request.set_status("404 Not Found") + request.forward(request.get_404_route()) + end + end + + if not request.get_redirect_data() then + request.process_forward() + local view = request.get_view() + if string.find(view, "^_") then + error("Tried to call a private view (prefixed with underscore).") + end + execute.filtered_view{ + module = request.get_module(), + view = view, + } + end + + -- force error due to missing absolute base URL until its too late to display error message + --if request.get_redirect_data() then + -- request.get_absolute_baseurl() + --end + + end, + + function(errobj) + return { + errobj = errobj, + stacktrace = string.gsub( + debug.traceback('', 2), + "^\r?\n?stack traceback:\r?\n?", "" + ) + } + end + ) + + if not success then trace.error{} end + + -- TODO: extend trace system to generally monitor execution time + -- trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() } + + slot.select('trace', trace.render) -- render trace information + + local redirect_data = request.get_redirect_data() + + -- log error and switch to error layout, unless success + if not success then + local errobj = error_info.errobj + local stacktrace = error_info.stacktrace + if not request._status then + request._status = "500 Internal Server Error" + end + slot.set_layout('system_error') + slot.select('system_error', function() + if getmetatable(errobj) == mondelefant.errorobject_metatable then + slot.put( + "
Database error of class ",
+ encode.html(errobj.code),
+ " occured:
",
+ encode.html(errobj.message),
+ "
", encode.html(tostring(errobj)), "
") + end + slot.put("Stack trace follows:
")
+ slot.put(encode.html_newlines(encode.html(stacktrace)))
+ slot.put("
Database error of class ",
- encode.html(errobj.code),
- " occured:
",
- encode.html(errobj.message),
- "
", encode.html(tostring(errobj)), "
") - end - slot.put("Stack trace follows:
")
- slot.put(encode.html_newlines(encode.html(stacktrace)))
- slot.put("