jbe@215: --[[-- jbe@215: request.handler( jbe@264: request, -- HTTP request object jbe@264: close -- boolean indicating whether the server should announce to close the connection jbe@215: ) jbe@215: jbe@255: Called by mcp.lua to process an HTTP request. Performs some initializations, calls request.router(), and handles the request. jbe@215: jbe@215: --]]-- jbe@210: jbe@255: local function file_exists(filename) jbe@255: local file = io.open(filename, "r") jbe@255: if file then jbe@255: io.close(file) jbe@255: return true jbe@255: else jbe@255: return false jbe@255: end jbe@255: end jbe@255: jbe@255: -- TODO: function incomplete yet jbe@264: function request.handler(http_request, close) jbe@258: _G.app = {} -- may be overwritten or modified by request initializers jbe@255: do jbe@255: request._in_progress = true -- NOTE: must be set to true before initializer functions are called jbe@255: for i, func in ipairs(request._initializers) do jbe@262: func() jbe@255: end jbe@255: end jbe@255: jbe@212: request._http_request = http_request jbe@215: local path = http_request.path jbe@215: if path then jbe@221: local relative_baseurl_elements = {} jbe@215: for match in string.gmatch(path, "/") do jbe@221: relative_baseurl_elements[#relative_baseurl_elements+1] = "../" jbe@215: end jbe@221: request._relative_baseurl = table.concat(relative_baseurl_elements) jbe@215: else jbe@215: request._relative_baseurl = nil jbe@215: end jbe@241: request._route = request.router() or {} jbe@255: jbe@264: if close then jbe@264: request.add_header("Connection", "close") jbe@264: end jbe@264: jbe@255: local success, error_info = xpcall( jbe@255: function() jbe@255: jbe@255: if request._route.static then jbe@255: local f = assert(io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r")) jbe@255: local d = f:read("*a") jbe@255: f:close() jbe@255: slot.put_into("data", d) jbe@255: slot.set_layout(nil, "application/octet-stream") -- TODO jbe@255: return jbe@255: end jbe@255: jbe@255: -- restore slots if coming from http redirect jbe@255: local tempstore_value = http_request.get_params["_tempstore"] jbe@255: if tempstore_value then jbe@255: trace.restore_slots{} jbe@255: local blob = tempstore.pop(tempstore_value) jbe@255: if blob then slot.restore_all(blob) end jbe@255: end jbe@255: jbe@255: if request.get_action() then jbe@255: trace.request{ jbe@255: module = request.get_module(), jbe@255: action = request.get_action() jbe@255: } jbe@255: if jbe@255: request.get_404_route() and jbe@255: not file_exists( jbe@255: encode.action_file_path{ jbe@255: module = request.get_module(), jbe@255: action = request.get_action() jbe@255: } jbe@255: ) jbe@255: then jbe@255: request.set_status("404 Not Found") jbe@255: request.forward(request.get_404_route()) jbe@255: else jbe@255: if http_request.method ~= "POST" then jbe@255: request.set_status("405 Method Not Allowed") jbe@255: request.add_header("Allow", "POST") jbe@255: error("Tried to invoke an action with a GET request.") jbe@255: end jbe@255: local action_status = execute.filtered_action{ jbe@255: module = request.get_module(), jbe@255: action = request.get_action(), jbe@255: } jbe@255: if not request.is_rerouted() then jbe@255: local routing_mode, routing_module, routing_view, routing_anchor jbe@255: routing_mode = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] jbe@255: routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"] jbe@255: routing_view = http_request.post_params["_webmcp_routing." .. action_status .. ".view"] jbe@255: routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] jbe@255: if not (routing_mode or routing_module or routing_view) then jbe@255: action_status = "default" jbe@255: routing_mode = http_request.post_params["_webmcp_routing.default.mode"] jbe@255: routing_module = http_request.post_params["_webmcp_routing.default.module"] jbe@255: routing_view = http_request.post_params["_webmcp_routing.default.view"] jbe@255: routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"] jbe@255: end jbe@255: assert(routing_module, "Routing information has no module.") jbe@255: assert(routing_view, "Routing information has no view.") jbe@255: if routing_mode == "redirect" then jbe@255: local routing_params = {} jbe@255: for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do jbe@255: local status, stripped_key = string.match( jbe@255: key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" jbe@255: ) jbe@255: if status == action_status then jbe@255: routing_params[stripped_key] = value jbe@255: end jbe@255: end jbe@255: request.redirect{ jbe@255: module = routing_module, jbe@255: view = routing_view, jbe@255: id = http_request.post_params["_webmcp_routing." .. action_status .. ".id"], jbe@255: params = routing_params, jbe@255: anchor = routing_anchor jbe@255: } jbe@255: elseif routing_mode == "forward" then jbe@255: request.forward{ module = routing_module, view = routing_view } jbe@255: else jbe@255: error("Missing or unknown routing mode in request parameters.") jbe@255: end jbe@255: end jbe@255: end jbe@255: else jbe@255: -- no action jbe@255: trace.request{ jbe@255: module = request.get_module(), jbe@255: view = request.get_view() jbe@255: } jbe@255: if jbe@255: request.get_404_route() and jbe@255: not file_exists( jbe@255: encode.view_file_path{ jbe@255: module = request.get_module(), jbe@255: view = request.get_view() jbe@255: } jbe@255: ) jbe@255: then jbe@255: request.set_status("404 Not Found") jbe@255: request.forward(request.get_404_route()) jbe@255: end jbe@255: end jbe@255: jbe@255: if not request.get_redirect_data() then jbe@255: request.process_forward() jbe@255: local view = request.get_view() jbe@255: if string.find(view, "^_") then jbe@255: error("Tried to call a private view (prefixed with underscore).") jbe@255: end jbe@255: execute.filtered_view{ jbe@255: module = request.get_module(), jbe@255: view = view, jbe@255: } jbe@255: end jbe@255: jbe@255: -- force error due to missing absolute base URL until its too late to display error message jbe@255: --if request.get_redirect_data() then jbe@255: -- request.get_absolute_baseurl() jbe@255: --end jbe@255: jbe@255: end, jbe@255: jbe@255: function(errobj) jbe@255: return { jbe@255: errobj = errobj, jbe@255: stacktrace = string.gsub( jbe@255: debug.traceback('', 2), jbe@255: "^\r?\n?stack traceback:\r?\n?", "" jbe@255: ) jbe@255: } jbe@255: end jbe@255: ) jbe@255: jbe@255: if not success then trace.error{} end jbe@255: jbe@255: -- TODO: extend trace system to generally monitor execution time jbe@255: -- trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() } jbe@255: jbe@255: slot.select('trace', trace.render) -- render trace information jbe@255: jbe@255: local redirect_data = request.get_redirect_data() jbe@255: jbe@255: -- log error and switch to error layout, unless success jbe@255: if not success then jbe@255: local errobj = error_info.errobj jbe@255: local stacktrace = error_info.stacktrace jbe@255: if not request._status then jbe@255: request._status = "500 Internal Server Error" jbe@255: end jbe@255: slot.set_layout('system_error') jbe@255: slot.select('system_error', function() jbe@255: if getmetatable(errobj) == mondelefant.errorobject_metatable then jbe@255: slot.put( jbe@255: "

Database error of class ", jbe@255: encode.html(errobj.code), jbe@255: " occured:
", jbe@255: encode.html(errobj.message), jbe@255: "

" jbe@255: ) jbe@255: else jbe@255: slot.put("

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

") jbe@255: end jbe@255: slot.put("

Stack trace follows:
") jbe@255: slot.put(encode.html_newlines(encode.html(stacktrace))) jbe@255: slot.put("

") jbe@255: end) jbe@255: elseif redirect_data then jbe@255: local redirect_params = {} jbe@255: for key, value in pairs(redirect_data.params) do jbe@255: redirect_params[key] = value jbe@255: end jbe@255: local slot_dump = slot.dump_all() jbe@255: if slot_dump ~= "" then jbe@255: redirect_params.tempstore = tempstore.save(slot_dump) jbe@255: end jbe@255: http_request:send_status("303 See Other") jbe@264: for i, header in ipairs(request._response_headers) do jbe@264: http_request:send_header(header[1], header[2]) jbe@264: end jbe@255: http_request:send_header( jbe@255: "Location", jbe@255: encode.url{ jbe@255: base = request.get_absolute_baseurl(), jbe@255: module = redirect_data.module, jbe@255: view = redirect_data.view, jbe@255: id = redirect_data.id, jbe@255: params = redirect_params, jbe@255: anchor = redirect_data.anchor jbe@255: } jbe@255: ) jbe@255: http_request:finish() jbe@255: end jbe@255: jbe@255: if not success or not redirect_data then jbe@255: jbe@255: http_request:send_status(request._status or "200 OK") jbe@255: for i, header in ipairs(request._response_headers) do jbe@255: http_request:send_header(header[1], header[2]) jbe@255: end jbe@255: http_request:send_header("Content-Type", slot.get_content_type()) jbe@255: http_request:send_data(slot.render_layout()) jbe@255: http_request:finish() jbe@255: end jbe@255: jbe@215: end jbe@258: jbe@258: --//-- jbe@258: jbe@258: --[[-- jbe@258: app -- table to store an application state jbe@258: jbe@258: 'app' is a global table for storing any application state data. It will be reset for every request. jbe@258: --]]-- jbe@258: jbe@258: -- Initialized in request.handler(...). jbe@258: jbe@258: --//--