# 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), + "

" + ) + else + slot.put("

", encode.html(tostring(errobj)), "

") + end + slot.put("

Stack trace follows:
") + slot.put(encode.html_newlines(encode.html(stacktrace))) + slot.put("

") + end) + elseif redirect_data then + local redirect_params = {} + for key, value in pairs(redirect_data.params) do + redirect_params[key] = value + end + local slot_dump = slot.dump_all() + if slot_dump ~= "" then + redirect_params.tempstore = tempstore.save(slot_dump) + end + http_request:send_status("303 See Other") + http_request:send_header("Connection", "close") -- TODO: extend moonbridge + http_request:send_header( + "Location", + encode.url{ + base = request.get_absolute_baseurl(), + module = redirect_data.module, + view = redirect_data.view, + id = redirect_data.id, + params = redirect_params, + anchor = redirect_data.anchor + } + ) + http_request:finish() + end + + if not success or not redirect_data then + + http_request:send_status(request._status or "200 OK") + http_request:send_header("Connection", "close") -- TODO: extend moonbridge + for i, header in ipairs(request._response_headers) do + http_request:send_header(header[1], header[2]) + end + http_request:send_header("Content-Type", slot.get_content_type()) + http_request:send_data(slot.render_layout()) + http_request:finish() + end + end - ---//-- diff -r 2169a62e12f5 -r 9e4be058959d framework/env/request/process.lua --- a/framework/env/request/process.lua Mon Mar 02 01:15:34 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ --- TODO: function incomplete yet - -local function file_exists(filename) - local file = io.open(filename, "r") - if file then - io.close(file) - return true - else - return false - end -end - -function 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 = request._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 request._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 = request._http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] - routing_module = request._http_request.post_params["_webmcp_routing." .. action_status .. ".module"] - routing_view = request._http_request.post_params["_webmcp_routing." .. action_status .. ".view"] - routing_anchor = request._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 = request._http_request.post_params["_webmcp_routing.default.mode"] - routing_module = request._http_request.post_params["_webmcp_routing.default.module"] - routing_view = request._http_request.post_params["_webmcp_routing.default.view"] - routing_anchor = request._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 = request._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), - "

" - ) - else - slot.put("

", encode.html(tostring(errobj)), "

") - end - slot.put("

Stack trace follows:
") - slot.put(encode.html_newlines(encode.html(stacktrace))) - slot.put("

") - end) - elseif redirect_data then - local redirect_params = {} - for key, value in pairs(redirect_data.params) do - redirect_params[key] = value - end - local slot_dump = slot.dump_all() - if slot_dump ~= "" then - redirect_params.tempstore = tempstore.save(slot_dump) - end - request._http_request:send_status("303 See Other") - request._http_request:send_header("Connection", "close") -- TODO: extend moonbridge - request._http_request:send_header( - "Location", - encode.url{ - base = request.get_absolute_baseurl(), - module = redirect_data.module, - view = redirect_data.view, - id = redirect_data.id, - params = redirect_params, - anchor = redirect_data.anchor - } - ) - request._http_request:finish() - end - - if not success or not redirect_data then - - request._http_request:send_status(request._status or "200 OK") - request._http_request:send_header("Connection", "close") -- TODO: extend moonbridge - for i, header in ipairs(request._response_headers) do - request._http_request:send_header(header[1], header[2]) - end - request._http_request:send_header("Content-Type", slot.get_content_type()) - request._http_request:send_data(slot.render_layout()) - request._http_request:finish() - end - -end