webmcp
changeset 255:9e4be058959d
New functions request.add_initializer(...) and request.add_variable(...) to allow per-request initialization; Merged request.process() with request.handler(...)
author | jbe |
---|---|
date | Sat Mar 14 23:39:47 2015 +0100 (2015-03-14) |
parents | 2169a62e12f5 |
children | 4654d59a4079 |
files | framework/env/request/__init.lua framework/env/request/add_initializer.lua framework/env/request/add_variable.lua framework/env/request/handler.lua framework/env/request/process.lua |
line diff
1.1 --- a/framework/env/request/__init.lua Mon Mar 02 01:15:34 2015 +0100 1.2 +++ b/framework/env/request/__init.lua Sat Mar 14 23:39:47 2015 +0100 1.3 @@ -1,16 +1,22 @@ 1.4 -request._http_request = nil 1.5 +request._initializers = {} 1.6 +request._in_progress = false 1.7 + 1.8 +-- initialize once 1.9 +request._absolute_baseurl = nil 1.10 request._http_options = {} 1.11 -request._response_headers = {} 1.12 1.13 -request._status = nil 1.14 -request._forward = nil 1.15 -request._forward_processed = false 1.16 -request._redirect = nil 1.17 -request._absolute_baseurl = nil 1.18 -request._404_route = nil 1.19 -request._force_absolute_baseurl = false 1.20 -request._perm_params = {} 1.21 -request._csrf_secret = nil 1.22 +-- initialize once and re-initialize per request 1.23 +request.add_variable(request, "_response_headers", {}) 1.24 +request.add_variable(request, "_force_absolute_baseurl", false) 1.25 +request.add_variable(request, "_perm_params", {}) 1.26 +request.add_variable(request, "_404_route", nil) 1.27 1.28 -request._params = {} 1.29 - 1.30 +-- initialize per request 1.31 +request.add_initializer(function() 1.32 + request._http_request = nil 1.33 + request._status = nil 1.34 + request._forward = nil 1.35 + request._forward_processed = false 1.36 + request._redirect = nil 1.37 + request._csrf_secret = nil 1.38 +end)
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/framework/env/request/add_initializer.lua Sat Mar 14 23:39:47 2015 +0100 2.3 @@ -0,0 +1,16 @@ 2.4 +--[[-- 2.5 +request.add_initializer( 2.6 + func -- function to be called on every request 2.7 +) 2.8 + 2.9 +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. 2.10 + 2.11 +--]]-- 2.12 + 2.13 +function request.add_initializer(func) 2.14 + local initializers = request._initializers 2.15 + initializers[#initializers+1] = func 2.16 + if request._in_progress then 2.17 + func(true) 2.18 + end 2.19 +end
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/framework/env/request/add_variable.lua Sat Mar 14 23:39:47 2015 +0100 3.3 @@ -0,0 +1,22 @@ 3.4 +--[[-- 3.5 +request.add_variable( 3.6 + tbl, -- table where the variable is stored 3.7 + key, -- name of variable (key within the table) 3.8 + value -- optional value for initialization 3.9 +) 3.10 + 3.11 +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. 3.12 + 3.13 +--]]-- 3.14 + 3.15 +function request.add_variable(tbl, key, value) 3.16 + local initialized_value 3.17 + tbl[key] = value 3.18 + request.add_initializer(function(first) 3.19 + if first then 3.20 + initialized_value = tbl[key] 3.21 + else 3.22 + tbl[key] = initialized_value 3.23 + end 3.24 + end) 3.25 +end
4.1 --- a/framework/env/request/handler.lua Mon Mar 02 01:15:34 2015 +0100 4.2 +++ b/framework/env/request/handler.lua Sat Mar 14 23:39:47 2015 +0100 4.3 @@ -3,11 +3,30 @@ 4.4 request -- HTTP request object 4.5 ) 4.6 4.7 -Called by mcp.lua to process an HTTP request. Performs some initializations, then calls request.router(). 4.8 +Called by mcp.lua to process an HTTP request. Performs some initializations, calls request.router(), and handles the request. 4.9 4.10 --]]-- 4.11 4.12 +local function file_exists(filename) 4.13 + local file = io.open(filename, "r") 4.14 + if file then 4.15 + io.close(file) 4.16 + return true 4.17 + else 4.18 + return false 4.19 + end 4.20 +end 4.21 + 4.22 +-- TODO: function incomplete yet 4.23 function request.handler(http_request) 4.24 + do 4.25 + local first = not request._in_progress 4.26 + request._in_progress = true -- NOTE: must be set to true before initializer functions are called 4.27 + for i, func in ipairs(request._initializers) do 4.28 + func(first) 4.29 + end 4.30 + end 4.31 + 4.32 request._http_request = http_request 4.33 local path = http_request.path 4.34 if path then 4.35 @@ -20,7 +39,210 @@ 4.36 request._relative_baseurl = nil 4.37 end 4.38 request._route = request.router() or {} 4.39 - request.process() 4.40 + 4.41 + local success, error_info = xpcall( 4.42 + function() 4.43 + 4.44 + if request._route.static then 4.45 + local f = assert(io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r")) 4.46 + local d = f:read("*a") 4.47 + f:close() 4.48 + slot.put_into("data", d) 4.49 + slot.set_layout(nil, "application/octet-stream") -- TODO 4.50 + return 4.51 + end 4.52 + 4.53 + -- restore slots if coming from http redirect 4.54 + local tempstore_value = http_request.get_params["_tempstore"] 4.55 + if tempstore_value then 4.56 + trace.restore_slots{} 4.57 + local blob = tempstore.pop(tempstore_value) 4.58 + if blob then slot.restore_all(blob) end 4.59 + end 4.60 + 4.61 + if request.get_action() then 4.62 + trace.request{ 4.63 + module = request.get_module(), 4.64 + action = request.get_action() 4.65 + } 4.66 + if 4.67 + request.get_404_route() and 4.68 + not file_exists( 4.69 + encode.action_file_path{ 4.70 + module = request.get_module(), 4.71 + action = request.get_action() 4.72 + } 4.73 + ) 4.74 + then 4.75 + request.set_status("404 Not Found") 4.76 + request.forward(request.get_404_route()) 4.77 + else 4.78 + if http_request.method ~= "POST" then 4.79 + request.set_status("405 Method Not Allowed") 4.80 + request.add_header("Allow", "POST") 4.81 + error("Tried to invoke an action with a GET request.") 4.82 + end 4.83 + local action_status = execute.filtered_action{ 4.84 + module = request.get_module(), 4.85 + action = request.get_action(), 4.86 + } 4.87 + if not request.is_rerouted() then 4.88 + local routing_mode, routing_module, routing_view, routing_anchor 4.89 + routing_mode = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] 4.90 + routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"] 4.91 + routing_view = http_request.post_params["_webmcp_routing." .. action_status .. ".view"] 4.92 + routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] 4.93 + if not (routing_mode or routing_module or routing_view) then 4.94 + action_status = "default" 4.95 + routing_mode = http_request.post_params["_webmcp_routing.default.mode"] 4.96 + routing_module = http_request.post_params["_webmcp_routing.default.module"] 4.97 + routing_view = http_request.post_params["_webmcp_routing.default.view"] 4.98 + routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"] 4.99 + end 4.100 + assert(routing_module, "Routing information has no module.") 4.101 + assert(routing_view, "Routing information has no view.") 4.102 + if routing_mode == "redirect" then 4.103 + local routing_params = {} 4.104 + for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do 4.105 + local status, stripped_key = string.match( 4.106 + key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" 4.107 + ) 4.108 + if status == action_status then 4.109 + routing_params[stripped_key] = value 4.110 + end 4.111 + end 4.112 + request.redirect{ 4.113 + module = routing_module, 4.114 + view = routing_view, 4.115 + id = http_request.post_params["_webmcp_routing." .. action_status .. ".id"], 4.116 + params = routing_params, 4.117 + anchor = routing_anchor 4.118 + } 4.119 + elseif routing_mode == "forward" then 4.120 + request.forward{ module = routing_module, view = routing_view } 4.121 + else 4.122 + error("Missing or unknown routing mode in request parameters.") 4.123 + end 4.124 + end 4.125 + end 4.126 + else 4.127 + -- no action 4.128 + trace.request{ 4.129 + module = request.get_module(), 4.130 + view = request.get_view() 4.131 + } 4.132 + if 4.133 + request.get_404_route() and 4.134 + not file_exists( 4.135 + encode.view_file_path{ 4.136 + module = request.get_module(), 4.137 + view = request.get_view() 4.138 + } 4.139 + ) 4.140 + then 4.141 + request.set_status("404 Not Found") 4.142 + request.forward(request.get_404_route()) 4.143 + end 4.144 + end 4.145 + 4.146 + if not request.get_redirect_data() then 4.147 + request.process_forward() 4.148 + local view = request.get_view() 4.149 + if string.find(view, "^_") then 4.150 + error("Tried to call a private view (prefixed with underscore).") 4.151 + end 4.152 + execute.filtered_view{ 4.153 + module = request.get_module(), 4.154 + view = view, 4.155 + } 4.156 + end 4.157 + 4.158 + -- force error due to missing absolute base URL until its too late to display error message 4.159 + --if request.get_redirect_data() then 4.160 + -- request.get_absolute_baseurl() 4.161 + --end 4.162 + 4.163 + end, 4.164 + 4.165 + function(errobj) 4.166 + return { 4.167 + errobj = errobj, 4.168 + stacktrace = string.gsub( 4.169 + debug.traceback('', 2), 4.170 + "^\r?\n?stack traceback:\r?\n?", "" 4.171 + ) 4.172 + } 4.173 + end 4.174 + ) 4.175 + 4.176 + if not success then trace.error{} end 4.177 + 4.178 + -- TODO: extend trace system to generally monitor execution time 4.179 + -- trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() } 4.180 + 4.181 + slot.select('trace', trace.render) -- render trace information 4.182 + 4.183 + local redirect_data = request.get_redirect_data() 4.184 + 4.185 + -- log error and switch to error layout, unless success 4.186 + if not success then 4.187 + local errobj = error_info.errobj 4.188 + local stacktrace = error_info.stacktrace 4.189 + if not request._status then 4.190 + request._status = "500 Internal Server Error" 4.191 + end 4.192 + slot.set_layout('system_error') 4.193 + slot.select('system_error', function() 4.194 + if getmetatable(errobj) == mondelefant.errorobject_metatable then 4.195 + slot.put( 4.196 + "<p>Database error of class <b>", 4.197 + encode.html(errobj.code), 4.198 + "</b> occured:<br/><b>", 4.199 + encode.html(errobj.message), 4.200 + "</b></p>" 4.201 + ) 4.202 + else 4.203 + slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>") 4.204 + end 4.205 + slot.put("<p>Stack trace follows:<br/>") 4.206 + slot.put(encode.html_newlines(encode.html(stacktrace))) 4.207 + slot.put("</p>") 4.208 + end) 4.209 + elseif redirect_data then 4.210 + local redirect_params = {} 4.211 + for key, value in pairs(redirect_data.params) do 4.212 + redirect_params[key] = value 4.213 + end 4.214 + local slot_dump = slot.dump_all() 4.215 + if slot_dump ~= "" then 4.216 + redirect_params.tempstore = tempstore.save(slot_dump) 4.217 + end 4.218 + http_request:send_status("303 See Other") 4.219 + http_request:send_header("Connection", "close") -- TODO: extend moonbridge 4.220 + http_request:send_header( 4.221 + "Location", 4.222 + encode.url{ 4.223 + base = request.get_absolute_baseurl(), 4.224 + module = redirect_data.module, 4.225 + view = redirect_data.view, 4.226 + id = redirect_data.id, 4.227 + params = redirect_params, 4.228 + anchor = redirect_data.anchor 4.229 + } 4.230 + ) 4.231 + http_request:finish() 4.232 + end 4.233 + 4.234 + if not success or not redirect_data then 4.235 + 4.236 + http_request:send_status(request._status or "200 OK") 4.237 + http_request:send_header("Connection", "close") -- TODO: extend moonbridge 4.238 + for i, header in ipairs(request._response_headers) do 4.239 + http_request:send_header(header[1], header[2]) 4.240 + end 4.241 + http_request:send_header("Content-Type", slot.get_content_type()) 4.242 + http_request:send_data(slot.render_layout()) 4.243 + http_request:finish() 4.244 + end 4.245 + 4.246 end 4.247 - 4.248 ---//--
5.1 --- a/framework/env/request/process.lua Mon Mar 02 01:15:34 2015 +0100 5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 5.3 @@ -1,220 +0,0 @@ 5.4 --- TODO: function incomplete yet 5.5 - 5.6 -local function file_exists(filename) 5.7 - local file = io.open(filename, "r") 5.8 - if file then 5.9 - io.close(file) 5.10 - return true 5.11 - else 5.12 - return false 5.13 - end 5.14 -end 5.15 - 5.16 -function request.process() 5.17 - 5.18 - local success, error_info = xpcall( 5.19 - function() 5.20 - 5.21 - if request._route.static then 5.22 - local f = assert(io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r")) 5.23 - local d = f:read("*a") 5.24 - f:close() 5.25 - slot.put_into("data", d) 5.26 - slot.set_layout(nil, "application/octet-stream") -- TODO 5.27 - return 5.28 - end 5.29 - 5.30 - -- restore slots if coming from http redirect 5.31 - local tempstore_value = request._http_request.get_params["_tempstore"] 5.32 - if tempstore_value then 5.33 - trace.restore_slots{} 5.34 - local blob = tempstore.pop(tempstore_value) 5.35 - if blob then slot.restore_all(blob) end 5.36 - end 5.37 - 5.38 - if request.get_action() then 5.39 - trace.request{ 5.40 - module = request.get_module(), 5.41 - action = request.get_action() 5.42 - } 5.43 - if 5.44 - request.get_404_route() and 5.45 - not file_exists( 5.46 - encode.action_file_path{ 5.47 - module = request.get_module(), 5.48 - action = request.get_action() 5.49 - } 5.50 - ) 5.51 - then 5.52 - request.set_status("404 Not Found") 5.53 - request.forward(request.get_404_route()) 5.54 - else 5.55 - if request._http_request.method ~= "POST" then 5.56 - request.set_status("405 Method Not Allowed") 5.57 - request.add_header("Allow", "POST") 5.58 - error("Tried to invoke an action with a GET request.") 5.59 - end 5.60 - local action_status = execute.filtered_action{ 5.61 - module = request.get_module(), 5.62 - action = request.get_action(), 5.63 - } 5.64 - if not request.is_rerouted() then 5.65 - local routing_mode, routing_module, routing_view, routing_anchor 5.66 - routing_mode = request._http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] 5.67 - routing_module = request._http_request.post_params["_webmcp_routing." .. action_status .. ".module"] 5.68 - routing_view = request._http_request.post_params["_webmcp_routing." .. action_status .. ".view"] 5.69 - routing_anchor = request._http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] 5.70 - if not (routing_mode or routing_module or routing_view) then 5.71 - action_status = "default" 5.72 - routing_mode = request._http_request.post_params["_webmcp_routing.default.mode"] 5.73 - routing_module = request._http_request.post_params["_webmcp_routing.default.module"] 5.74 - routing_view = request._http_request.post_params["_webmcp_routing.default.view"] 5.75 - routing_anchor = request._http_request.post_params["_webmcp_routing.default.anchor"] 5.76 - end 5.77 - assert(routing_module, "Routing information has no module.") 5.78 - assert(routing_view, "Routing information has no view.") 5.79 - if routing_mode == "redirect" then 5.80 - local routing_params = {} 5.81 - for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do 5.82 - local status, stripped_key = string.match( 5.83 - key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" 5.84 - ) 5.85 - if status == action_status then 5.86 - routing_params[stripped_key] = value 5.87 - end 5.88 - end 5.89 - request.redirect{ 5.90 - module = routing_module, 5.91 - view = routing_view, 5.92 - id = request._http_request.post_params["_webmcp_routing." .. action_status .. ".id"], 5.93 - params = routing_params, 5.94 - anchor = routing_anchor 5.95 - } 5.96 - elseif routing_mode == "forward" then 5.97 - request.forward{ module = routing_module, view = routing_view } 5.98 - else 5.99 - error("Missing or unknown routing mode in request parameters.") 5.100 - end 5.101 - end 5.102 - end 5.103 - else 5.104 - -- no action 5.105 - trace.request{ 5.106 - module = request.get_module(), 5.107 - view = request.get_view() 5.108 - } 5.109 - if 5.110 - request.get_404_route() and 5.111 - not file_exists( 5.112 - encode.view_file_path{ 5.113 - module = request.get_module(), 5.114 - view = request.get_view() 5.115 - } 5.116 - ) 5.117 - then 5.118 - request.set_status("404 Not Found") 5.119 - request.forward(request.get_404_route()) 5.120 - end 5.121 - end 5.122 - 5.123 - if not request.get_redirect_data() then 5.124 - request.process_forward() 5.125 - local view = request.get_view() 5.126 - if string.find(view, "^_") then 5.127 - error("Tried to call a private view (prefixed with underscore).") 5.128 - end 5.129 - execute.filtered_view{ 5.130 - module = request.get_module(), 5.131 - view = view, 5.132 - } 5.133 - end 5.134 - 5.135 - -- force error due to missing absolute base URL until its too late to display error message 5.136 - --if request.get_redirect_data() then 5.137 - -- request.get_absolute_baseurl() 5.138 - --end 5.139 - 5.140 - end, 5.141 - 5.142 - function(errobj) 5.143 - return { 5.144 - errobj = errobj, 5.145 - stacktrace = string.gsub( 5.146 - debug.traceback('', 2), 5.147 - "^\r?\n?stack traceback:\r?\n?", "" 5.148 - ) 5.149 - } 5.150 - end 5.151 - ) 5.152 - 5.153 - if not success then trace.error{} end 5.154 - 5.155 - -- TODO: extend trace system to generally monitor execution time 5.156 - -- trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() } 5.157 - 5.158 - slot.select('trace', trace.render) -- render trace information 5.159 - 5.160 - local redirect_data = request.get_redirect_data() 5.161 - 5.162 - -- log error and switch to error layout, unless success 5.163 - if not success then 5.164 - local errobj = error_info.errobj 5.165 - local stacktrace = error_info.stacktrace 5.166 - if not request._status then 5.167 - request._status = "500 Internal Server Error" 5.168 - end 5.169 - slot.set_layout('system_error') 5.170 - slot.select('system_error', function() 5.171 - if getmetatable(errobj) == mondelefant.errorobject_metatable then 5.172 - slot.put( 5.173 - "<p>Database error of class <b>", 5.174 - encode.html(errobj.code), 5.175 - "</b> occured:<br/><b>", 5.176 - encode.html(errobj.message), 5.177 - "</b></p>" 5.178 - ) 5.179 - else 5.180 - slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>") 5.181 - end 5.182 - slot.put("<p>Stack trace follows:<br/>") 5.183 - slot.put(encode.html_newlines(encode.html(stacktrace))) 5.184 - slot.put("</p>") 5.185 - end) 5.186 - elseif redirect_data then 5.187 - local redirect_params = {} 5.188 - for key, value in pairs(redirect_data.params) do 5.189 - redirect_params[key] = value 5.190 - end 5.191 - local slot_dump = slot.dump_all() 5.192 - if slot_dump ~= "" then 5.193 - redirect_params.tempstore = tempstore.save(slot_dump) 5.194 - end 5.195 - request._http_request:send_status("303 See Other") 5.196 - request._http_request:send_header("Connection", "close") -- TODO: extend moonbridge 5.197 - request._http_request:send_header( 5.198 - "Location", 5.199 - encode.url{ 5.200 - base = request.get_absolute_baseurl(), 5.201 - module = redirect_data.module, 5.202 - view = redirect_data.view, 5.203 - id = redirect_data.id, 5.204 - params = redirect_params, 5.205 - anchor = redirect_data.anchor 5.206 - } 5.207 - ) 5.208 - request._http_request:finish() 5.209 - end 5.210 - 5.211 - if not success or not redirect_data then 5.212 - 5.213 - request._http_request:send_status(request._status or "200 OK") 5.214 - request._http_request:send_header("Connection", "close") -- TODO: extend moonbridge 5.215 - for i, header in ipairs(request._response_headers) do 5.216 - request._http_request:send_header(header[1], header[2]) 5.217 - end 5.218 - request._http_request:send_header("Content-Type", slot.get_content_type()) 5.219 - request._http_request:send_data(slot.render_layout()) 5.220 - request._http_request:finish() 5.221 - end 5.222 - 5.223 -end