jbe@215: --[[-- jbe@327: success = -- false if an error occurred, true otherwise jbe@215: request.handler( jbe@270: http_request, -- HTTP request object jbe@215: ) jbe@215: jbe@316: Called by mcp.lua to process an HTTP request. Calls request.router(), and handles the request. Note: request initializers will have to be (automatically) executed before this function is invoked by mcp.lua. 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@264: function request.handler(http_request, close) 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@280: if #relative_baseurl_elements > 0 then jbe@280: request._relative_baseurl = table.concat(relative_baseurl_elements) jbe@280: else jbe@280: request._relative_baseurl = "./" jbe@280: end jbe@215: else jbe@215: request._relative_baseurl = nil jbe@215: end jbe@241: request._route = request.router() or {} jbe@275: do jbe@275: local post_id = http_request.post_params["_webmcp_id"] jbe@275: if post_id then jbe@275: request._route.id = post_id jbe@275: end jbe@275: end jbe@255: jbe@255: local success, error_info = xpcall( jbe@255: function() jbe@255: jbe@255: if request._route.static then jbe@270: local f, errmsg = io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r") jbe@270: if not f then jbe@328: request.set_status("404 Not Found") jbe@270: if request.get_404_route() then jbe@270: request.forward(request.get_404_route()) jbe@270: else jbe@270: error('Could not open static file "' .. request._route.static .. '": ' .. errmsg) jbe@270: end jbe@327: else jbe@327: local d = assert(f:read("*a")) jbe@327: f:close() jbe@327: slot.put_into("data", d) jbe@327: local filename_extension = string.match(request._route.static, "%.([^.]+)$") jbe@327: slot.set_layout(nil, request._mime_types[filename_extension] or "application/octet-stream") jbe@327: request.allow_caching() jbe@327: return jbe@270: end jbe@255: end jbe@255: jbe@255: -- restore slots if coming from http redirect jbe@327: do jbe@327: local tempstore_value = http_request.get_params["_tempstore"] jbe@327: if tempstore_value then jbe@327: trace.restore_slots{} jbe@327: local blob = tempstore.pop(tempstore_value) jbe@327: if blob then slot.restore_all(blob) end jbe@327: 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: 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@328: http_request:close_after_finish() 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@267: redirect_data = table.new(redirect_data) jbe@267: redirect_data.params = table.new(redirect_data.params) jbe@255: local slot_dump = slot.dump_all() jbe@255: if slot_dump ~= "" then jbe@312: redirect_data.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@267: http_request:send_header("Location", encode.url(redirect_data)) 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@291: if not request._cache_manual then jbe@291: local cache_time = request._cache_time jbe@291: if request._cache and cache_time and cache_time > 0 then jbe@291: http_request:send_header("Cache-Control", "max-age=" .. cache_time) jbe@291: else jbe@291: http_request:send_header("Cache-Control", "no-cache") jbe@291: end jbe@291: 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@327: return success jbe@327: jbe@215: end