webmcp
view framework/env/request/handler.lua @ 327:d8480a60a07d
Discard process after error
| author | jbe | 
|---|---|
| date | Tue Mar 24 01:08:32 2015 +0100 (2015-03-24) | 
| parents | e3e2a03f75b2 | 
| children | 04b0687130d8 | 
 line source
     1 --[[--
     2 success =         -- false if an error occurred, true otherwise
     3 request.handler(
     4   http_request,   -- HTTP request object
     5   close           -- boolean indicating whether the server should announce to close the connection
     6 )
     8 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.
    10 --]]--
    12 local function file_exists(filename)
    13   local file = io.open(filename, "r")
    14   if file then
    15     io.close(file)
    16     return true
    17   else
    18     return false
    19   end
    20 end
    22 function request.handler(http_request, close)
    23   local close_sent = false
    24   request._http_request = http_request
    25   local path = http_request.path
    26   if path then
    27     local relative_baseurl_elements = {}
    28     for match in string.gmatch(path, "/") do
    29       relative_baseurl_elements[#relative_baseurl_elements+1] = "../"
    30     end
    31     if #relative_baseurl_elements > 0 then
    32       request._relative_baseurl = table.concat(relative_baseurl_elements)
    33     else
    34       request._relative_baseurl = "./"
    35     end
    36   else
    37     request._relative_baseurl = nil
    38   end
    39   request._route = request.router() or {}
    40   do
    41     local post_id = http_request.post_params["_webmcp_id"]
    42     if post_id then
    43       request._route.id = post_id
    44     end
    45   end
    47   if close then
    48     request.add_header("Connection", "close")
    49     close_sent = true
    50   end
    52   local success, error_info = xpcall(
    53     function()
    55       if request._route.static then
    56         local f, errmsg = io.open(WEBMCP_BASE_PATH .. "static/" .. request._route.static, "r")
    57         if not f then
    58           if request.get_404_route() then
    59             request.set_status("404 Not Found")
    60             request.forward(request.get_404_route())
    61           else
    62             error('Could not open static file "' .. request._route.static .. '": ' .. errmsg)
    63           end
    64         else
    65           local d = assert(f:read("*a"))
    66           f:close()
    67           slot.put_into("data", d)
    68           local filename_extension = string.match(request._route.static, "%.([^.]+)$")
    69           slot.set_layout(nil, request._mime_types[filename_extension] or "application/octet-stream")
    70           request.allow_caching()
    71           return
    72         end
    73       end
    75       -- restore slots if coming from http redirect
    76       do
    77         local tempstore_value = http_request.get_params["_tempstore"]
    78         if tempstore_value then
    79           trace.restore_slots{}
    80           local blob = tempstore.pop(tempstore_value)
    81           if blob then slot.restore_all(blob) end
    82         end
    83       end
    85       if request.get_action() then
    86         trace.request{
    87           module = request.get_module(),
    88           action = request.get_action()
    89         }
    90         if
    91           request.get_404_route() and
    92           not file_exists(
    93             encode.action_file_path{
    94               module = request.get_module(),
    95               action = request.get_action()
    96             }
    97           )
    98         then
    99           request.set_status("404 Not Found")
   100           request.forward(request.get_404_route())
   101         else
   102           if http_request.method ~= "POST" then
   103             request.set_status("405 Method Not Allowed")
   104             request.add_header("Allow", "POST")
   105             error("Tried to invoke an action with a GET request.")
   106           end
   107           local action_status = execute.filtered_action{
   108             module = request.get_module(),
   109             action = request.get_action(),
   110           }
   111           if not request.is_rerouted() then
   112             local routing_mode, routing_module, routing_view, routing_anchor
   113             routing_mode   = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"]
   114             routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"]
   115             routing_view   = http_request.post_params["_webmcp_routing." .. action_status .. ".view"]
   116             routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"]
   117             if not (routing_mode or routing_module or routing_view) then
   118               action_status = "default"
   119               routing_mode   = http_request.post_params["_webmcp_routing.default.mode"]
   120               routing_module = http_request.post_params["_webmcp_routing.default.module"]
   121               routing_view   = http_request.post_params["_webmcp_routing.default.view"]
   122               routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"]
   123             end
   124             assert(routing_module, "Routing information has no module.")
   125             assert(routing_view,   "Routing information has no view.")
   126             if routing_mode == "redirect" then
   127               local routing_params = {}
   128               for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do
   129                 local status, stripped_key = string.match(
   130                   key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
   131                 )
   132                 if status == action_status then
   133                   routing_params[stripped_key] = value
   134                 end
   135               end
   136               request.redirect{
   137                 module = routing_module,
   138                 view   = routing_view,
   139                 id     = http_request.post_params["_webmcp_routing." .. action_status .. ".id"],
   140                 params = routing_params,
   141                 anchor = routing_anchor
   142               }
   143             elseif routing_mode == "forward" then
   144               request.forward{ module = routing_module, view = routing_view }
   145             else
   146               error("Missing or unknown routing mode in request parameters.")
   147             end
   148           end
   149         end
   150       else
   151         -- no action
   152         trace.request{
   153           module = request.get_module(),
   154           view   = request.get_view()
   155         }
   156         if
   157           request.get_404_route() and
   158           not file_exists(
   159             encode.view_file_path{
   160               module = request.get_module(),
   161               view   = request.get_view()
   162             }
   163           )
   164         then
   165           request.set_status("404 Not Found")
   166           request.forward(request.get_404_route())
   167         end
   168       end
   170       if not request.get_redirect_data() then
   171         request.process_forward()
   172         local view = request.get_view()
   173         if string.find(view, "^_") then
   174           error("Tried to call a private view (prefixed with underscore).")
   175         end
   176         execute.filtered_view{
   177           module = request.get_module(),
   178           view   = view,
   179         }
   180       end
   182     end,
   184     function(errobj)
   185       return {
   186         errobj = errobj,
   187         stacktrace = string.gsub(
   188           debug.traceback('', 2),
   189           "^\r?\n?stack traceback:\r?\n?", ""
   190         )
   191       }
   192     end
   193   )
   195   if not success then trace.error{} end
   197   -- TODO: extend trace system to generally monitor execution time
   198   -- trace.exectime{ real = extos.monotonic_hires_time(), cpu = os.clock() }
   200   slot.select('trace', trace.render)  -- render trace information
   202   local redirect_data = request.get_redirect_data()
   204   -- log error and switch to error layout, unless success
   205   if not success then
   206     local errobj     = error_info.errobj
   207     local stacktrace = error_info.stacktrace
   208     if not request._status then
   209       request._status = "500 Internal Server Error"
   210     end
   211     if not close_sent then
   212       request.add_header("Connection", "close")
   213     end
   214     slot.set_layout('system_error')
   215     slot.select('system_error', function()
   216       if getmetatable(errobj) == mondelefant.errorobject_metatable then
   217         slot.put(
   218           "<p>Database error of class <b>",
   219           encode.html(errobj.code),
   220           "</b> occured:<br/><b>",
   221           encode.html(errobj.message),
   222           "</b></p>"
   223         )
   224       else
   225         slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
   226       end
   227       slot.put("<p>Stack trace follows:<br/>")
   228       slot.put(encode.html_newlines(encode.html(stacktrace)))
   229       slot.put("</p>")
   230     end)
   231   elseif redirect_data then
   232     redirect_data = table.new(redirect_data)
   233     redirect_data.params = table.new(redirect_data.params)
   234     local slot_dump = slot.dump_all()
   235     if slot_dump ~= "" then
   236       redirect_data.params._tempstore = tempstore.save(slot_dump)
   237     end
   238     http_request:send_status("303 See Other")
   239     for i, header in ipairs(request._response_headers) do
   240       http_request:send_header(header[1], header[2])
   241     end
   242     http_request:send_header("Location", encode.url(redirect_data))
   243     http_request:finish()
   244   end
   246   if not success or not redirect_data then
   248     http_request:send_status(request._status or "200 OK")
   249     for i, header in ipairs(request._response_headers) do
   250       http_request:send_header(header[1], header[2])
   251     end
   252     if not request._cache_manual then
   253       local cache_time = request._cache_time
   254       if request._cache and cache_time and cache_time > 0 then
   255         http_request:send_header("Cache-Control", "max-age=" .. cache_time)
   256       else
   257         http_request:send_header("Cache-Control", "no-cache")
   258       end
   259     end
   260     http_request:send_header("Content-Type", slot.get_content_type())
   261     http_request:send_data(slot.render_layout())
   262     http_request:finish()
   263   end
   265   return success
   267 end
