| rev | line source | 
| jbe@215 | 1 --[[-- | 
| jbe@327 | 2 success =         -- false if an error occurred, true otherwise | 
| jbe@215 | 3 request.handler( | 
| jbe@349 | 4   http_request    -- HTTP request object | 
| jbe@215 | 5 ) | 
| jbe@215 | 6 | 
| jbe@494 | 7 Called by bin/mcp.lua to process an HTTP request. Calls request.router() and handles the request. Note: request initializers (see request.initialize()) are to be executed by mcp.lua before this function is invoked by mcp.lua. | 
| jbe@215 | 8 | 
| jbe@215 | 9 --]]-- | 
| jbe@210 | 10 | 
| jbe@347 | 11 function request.handler(http_request) | 
| jbe@459 | 12 | 
| jbe@212 | 13   request._http_request = http_request | 
| jbe@215 | 14   local path = http_request.path | 
| jbe@215 | 15   if path then | 
| jbe@221 | 16     local relative_baseurl_elements = {} | 
| jbe@215 | 17     for match in string.gmatch(path, "/") do | 
| jbe@221 | 18       relative_baseurl_elements[#relative_baseurl_elements+1] = "../" | 
| jbe@215 | 19     end | 
| jbe@280 | 20     if #relative_baseurl_elements > 0 then | 
| jbe@280 | 21       request._relative_baseurl = table.concat(relative_baseurl_elements) | 
| jbe@280 | 22     else | 
| jbe@280 | 23       request._relative_baseurl = "./" | 
| jbe@280 | 24     end | 
| jbe@215 | 25   else | 
| jbe@215 | 26     request._relative_baseurl = nil | 
| jbe@215 | 27   end | 
| jbe@255 | 28 | 
| jbe@255 | 29   local success, error_info = xpcall( | 
| jbe@255 | 30     function() | 
| jbe@255 | 31 | 
| jbe@459 | 32       local function require_method(errmsg, ...) | 
| jbe@461 | 33         for i = 1, select("#", ...) do | 
| jbe@459 | 34           if http_request.method == select(i, ...) then return end | 
| jbe@459 | 35         end | 
| jbe@459 | 36         request.set_status("405 Method Not Allowed") | 
| jbe@459 | 37         request.add_header("Allow", table.concat({...}, ", ")) | 
| jbe@459 | 38         error(errmsg) | 
| jbe@459 | 39       end | 
| jbe@459 | 40 | 
| jbe@459 | 41       request._route = request.router() | 
| jbe@459 | 42       do | 
| jbe@459 | 43         local post_id = http_request.post_params["_webmcp_id"] | 
| jbe@459 | 44         if post_id then | 
| jbe@459 | 45           request._route.id = post_id | 
| jbe@459 | 46         end | 
| jbe@459 | 47       end | 
| jbe@459 | 48       local options_handler = loadcached(encode.file_path( | 
| jbe@459 | 49         WEBMCP_BASE_PATH, 'app', WEBMCP_APP_NAME, 'http_options.lua' | 
| jbe@459 | 50       )) | 
| jbe@459 | 51       if options_handler then | 
| jbe@459 | 52         options_handler() | 
| jbe@459 | 53       end | 
| jbe@459 | 54       if http_request.method == "OPTIONS" then | 
| jbe@459 | 55         return | 
| jbe@459 | 56       end | 
| jbe@459 | 57 | 
| jbe@329 | 58       if not request._route then | 
| jbe@329 | 59         request._route = {} | 
| jbe@329 | 60         if request.get_404_route() then | 
| jbe@329 | 61           request.set_status("404 Not Found") | 
| jbe@329 | 62           request.forward(request.get_404_route()) | 
| jbe@329 | 63         else | 
| jbe@329 | 64           error("Could not route request URL") | 
| jbe@329 | 65         end | 
| jbe@329 | 66       end | 
| jbe@329 | 67 | 
| jbe@255 | 68       if request._route.static then | 
| jbe@358 | 69         local subpath = request._route.static | 
| jbe@491 | 70         local errmsg | 
| jbe@358 | 71         for element in string.gmatch(subpath, "[^/]+") do | 
| jbe@358 | 72           if element == "." or element == ".." then | 
| jbe@491 | 73             errmsg = "Illegal path" | 
| jbe@358 | 74             break | 
| jbe@358 | 75           end | 
| jbe@358 | 76         end | 
| jbe@491 | 77         local fstat, f | 
| jbe@491 | 78         if not errmsg then | 
| jbe@358 | 79           local filename = WEBMCP_BASE_PATH .. "static/" .. subpath | 
| jbe@358 | 80           fstat, errmsg = extos.stat(filename) | 
| jbe@358 | 81           if fstat then | 
| jbe@358 | 82             if fstat.isdir then | 
| jbe@358 | 83               errmsg = "Is a directory" | 
| jbe@358 | 84             elseif not fstat.isreg then | 
| jbe@358 | 85               errmsg = "Not a regular file" | 
| jbe@358 | 86             else | 
| jbe@358 | 87               f, errmsg = io.open(filename, "r") | 
| jbe@358 | 88             end | 
| jbe@347 | 89           end | 
| jbe@347 | 90         end | 
| jbe@270 | 91         if not f then | 
| jbe@270 | 92           if request.get_404_route() then | 
| jbe@329 | 93             request.set_status("404 Not Found") | 
| jbe@270 | 94             request.forward(request.get_404_route()) | 
| jbe@270 | 95           else | 
| jbe@358 | 96             error('Could not open static file "' .. subpath .. '": ' .. errmsg) | 
| jbe@270 | 97           end | 
| jbe@327 | 98         else | 
| jbe@459 | 99           require_method( | 
| jbe@459 | 100             "Invalid HTTP method for static file", | 
| jbe@459 | 101             "HEAD", "GET", "POST" | 
| jbe@459 | 102           ) | 
| jbe@327 | 103           local d = assert(f:read("*a")) | 
| jbe@327 | 104           f:close() | 
| jbe@327 | 105           slot.put_into("data", d) | 
| jbe@358 | 106           local filename_extension = string.match(subpath, "%.([^.]+)$") | 
| jbe@327 | 107           slot.set_layout(nil, request._mime_types[filename_extension] or "application/octet-stream") | 
| jbe@327 | 108           request.allow_caching() | 
| jbe@327 | 109           return | 
| jbe@270 | 110         end | 
| jbe@255 | 111       end | 
| jbe@255 | 112 | 
| jbe@255 | 113       -- restore slots if coming from http redirect | 
| jbe@327 | 114       do | 
| jbe@327 | 115         local tempstore_value = http_request.get_params["_tempstore"] | 
| jbe@327 | 116         if tempstore_value then | 
| jbe@327 | 117           trace.restore_slots{} | 
| jbe@327 | 118           local blob = tempstore.pop(tempstore_value) | 
| jbe@327 | 119           if blob then slot.restore_all(blob) end | 
| jbe@327 | 120         end | 
| jbe@255 | 121       end | 
| jbe@255 | 122 | 
| jbe@255 | 123       if request.get_action() then | 
| jbe@255 | 124         trace.request{ | 
| jbe@255 | 125           module = request.get_module(), | 
| jbe@255 | 126           action = request.get_action() | 
| jbe@255 | 127         } | 
| jbe@255 | 128         if | 
| jbe@352 | 129           not execute.action{ | 
| jbe@352 | 130             module = request.get_module(), | 
| jbe@352 | 131             action = request.get_action(), | 
| jbe@352 | 132             test_existence = true | 
| jbe@358 | 133           } and | 
| jbe@358 | 134           request.get_404_route() | 
| jbe@255 | 135         then | 
| jbe@255 | 136           request.set_status("404 Not Found") | 
| jbe@255 | 137           request.forward(request.get_404_route()) | 
| jbe@255 | 138         else | 
| jbe@459 | 139           require_method( | 
| jbe@459 | 140             "Invalid HTTP method for action (POST request required)", | 
| jbe@459 | 141             "POST" | 
| jbe@459 | 142           ) | 
| jbe@255 | 143           local action_status = execute.filtered_action{ | 
| jbe@255 | 144             module = request.get_module(), | 
| jbe@255 | 145             action = request.get_action(), | 
| jbe@255 | 146           } | 
| jbe@492 | 147           if not (request.is_rerouted() or slot.layout_is_set()) then | 
| jbe@255 | 148             local routing_mode, routing_module, routing_view, routing_anchor | 
| jbe@255 | 149             routing_mode   = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] | 
| jbe@255 | 150             routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"] | 
| jbe@255 | 151             routing_view   = http_request.post_params["_webmcp_routing." .. action_status .. ".view"] | 
| jbe@255 | 152             routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] | 
| jbe@255 | 153             if not (routing_mode or routing_module or routing_view) then | 
| jbe@255 | 154               action_status = "default" | 
| jbe@255 | 155               routing_mode   = http_request.post_params["_webmcp_routing.default.mode"] | 
| jbe@255 | 156               routing_module = http_request.post_params["_webmcp_routing.default.module"] | 
| jbe@255 | 157               routing_view   = http_request.post_params["_webmcp_routing.default.view"] | 
| jbe@255 | 158               routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"] | 
| jbe@255 | 159             end | 
| jbe@255 | 160             assert(routing_module, "Routing information has no module.") | 
| jbe@255 | 161             assert(routing_view,   "Routing information has no view.") | 
| jbe@255 | 162             if routing_mode == "redirect" then | 
| jbe@255 | 163               local routing_params = {} | 
| jbe@255 | 164               for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do | 
| jbe@255 | 165                 local status, stripped_key = string.match( | 
| jbe@255 | 166                   key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" | 
| jbe@255 | 167                 ) | 
| jbe@255 | 168                 if status == action_status then | 
| jbe@255 | 169                   routing_params[stripped_key] = value | 
| jbe@255 | 170                 end | 
| jbe@255 | 171               end | 
| jbe@255 | 172               request.redirect{ | 
| jbe@255 | 173                 module = routing_module, | 
| jbe@255 | 174                 view   = routing_view, | 
| jbe@255 | 175                 id     = http_request.post_params["_webmcp_routing." .. action_status .. ".id"], | 
| jbe@255 | 176                 params = routing_params, | 
| jbe@255 | 177                 anchor = routing_anchor | 
| jbe@255 | 178               } | 
| jbe@255 | 179             elseif routing_mode == "forward" then | 
| jbe@255 | 180               request.forward{ module = routing_module, view = routing_view } | 
| jbe@255 | 181             else | 
| jbe@255 | 182               error("Missing or unknown routing mode in request parameters.") | 
| jbe@255 | 183             end | 
| jbe@255 | 184           end | 
| jbe@255 | 185         end | 
| jbe@255 | 186       else | 
| jbe@255 | 187         -- no action | 
| jbe@255 | 188         trace.request{ | 
| jbe@255 | 189           module = request.get_module(), | 
| jbe@255 | 190           view   = request.get_view() | 
| jbe@255 | 191         } | 
| jbe@255 | 192         if | 
| jbe@352 | 193           not execute.view{ | 
| jbe@352 | 194             module = request.get_module(), | 
| jbe@352 | 195             view   = request.get_view(), | 
| jbe@352 | 196             test_existence = true | 
| jbe@358 | 197           } and request.get_404_route() | 
| jbe@255 | 198         then | 
| jbe@255 | 199           request.set_status("404 Not Found") | 
| jbe@255 | 200           request.forward(request.get_404_route()) | 
| jbe@255 | 201         end | 
| jbe@255 | 202       end | 
| jbe@255 | 203 | 
| jbe@496 | 204       if not (request.get_redirect_data() or slot.layout_is_set()) then | 
| jbe@255 | 205         request.process_forward() | 
| jbe@255 | 206         local view = request.get_view() | 
| jbe@255 | 207         if string.find(view, "^_") then | 
| jbe@255 | 208           error("Tried to call a private view (prefixed with underscore).") | 
| jbe@255 | 209         end | 
| jbe@459 | 210         require_method( | 
| jbe@459 | 211           "Invalid HTTP method", | 
| jbe@459 | 212           "HEAD", "GET", "POST" | 
| jbe@459 | 213         ) | 
| jbe@255 | 214         execute.filtered_view{ | 
| jbe@255 | 215           module = request.get_module(), | 
| jbe@255 | 216           view   = view, | 
| jbe@255 | 217         } | 
| jbe@255 | 218       end | 
| jbe@255 | 219 | 
| jbe@255 | 220     end, | 
| jbe@255 | 221 | 
| jbe@255 | 222     function(errobj) | 
| jbe@255 | 223       return { | 
| jbe@255 | 224         errobj = errobj, | 
| jbe@255 | 225         stacktrace = string.gsub( | 
| jbe@255 | 226           debug.traceback('', 2), | 
| jbe@255 | 227           "^\r?\n?stack traceback:\r?\n?", "" | 
| jbe@255 | 228         ) | 
| jbe@255 | 229       } | 
| jbe@255 | 230     end | 
| jbe@255 | 231   ) | 
| jbe@255 | 232 | 
| jbe@255 | 233   if not success then trace.error{} end | 
| jbe@255 | 234 | 
| jbe@255 | 235   slot.select('trace', trace.render)  -- render trace information | 
| jbe@255 | 236 | 
| jbe@255 | 237   local redirect_data = request.get_redirect_data() | 
| jbe@255 | 238 | 
| jbe@255 | 239   -- log error and switch to error layout, unless success | 
| jbe@255 | 240   if not success then | 
| jbe@255 | 241     local errobj     = error_info.errobj | 
| jbe@255 | 242     local stacktrace = error_info.stacktrace | 
| jbe@255 | 243     if not request._status then | 
| jbe@255 | 244       request._status = "500 Internal Server Error" | 
| jbe@255 | 245     end | 
| jbe@328 | 246     http_request:close_after_finish() | 
| jbe@255 | 247     slot.set_layout('system_error') | 
| jbe@255 | 248     slot.select('system_error', function() | 
| jbe@255 | 249       if getmetatable(errobj) == mondelefant.errorobject_metatable then | 
| jbe@255 | 250         slot.put( | 
| jbe@255 | 251           "<p>Database error of class <b>", | 
| jbe@255 | 252           encode.html(errobj.code), | 
| jbe@255 | 253           "</b> occured:<br/><b>", | 
| jbe@255 | 254           encode.html(errobj.message), | 
| jbe@255 | 255           "</b></p>" | 
| jbe@255 | 256         ) | 
| jbe@255 | 257       else | 
| jbe@255 | 258         slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>") | 
| jbe@255 | 259       end | 
| jbe@255 | 260       slot.put("<p>Stack trace follows:<br/>") | 
| jbe@255 | 261       slot.put(encode.html_newlines(encode.html(stacktrace))) | 
| jbe@255 | 262       slot.put("</p>") | 
| jbe@255 | 263     end) | 
| jbe@497 | 264     for i, error_handler in ipairs(request._error_handlers) do | 
| jbe@497 | 265       error_handler(error_info.errobj, error_info.stacktrace) | 
| jbe@497 | 266     end | 
| jbe@255 | 267   elseif redirect_data then | 
| jbe@455 | 268     if | 
| jbe@455 | 269       redirect_data.include_tempstore == true or ( | 
| jbe@455 | 270         redirect_data.include_tempstore ~= false and | 
| jbe@455 | 271         not redirect_data.external | 
| jbe@455 | 272       ) | 
| jbe@455 | 273     then | 
| jbe@455 | 274       redirect_data = table.new(redirect_data) | 
| jbe@454 | 275       redirect_data.params = table.new(redirect_data.params) | 
| jbe@454 | 276       local slot_dump = slot.dump_all() | 
| jbe@454 | 277       if slot_dump ~= "" then | 
| jbe@454 | 278         redirect_data.params._tempstore = tempstore.save(slot_dump) | 
| jbe@454 | 279       end | 
| jbe@255 | 280     end | 
| jbe@255 | 281     http_request:send_status("303 See Other") | 
| jbe@264 | 282     for i, header in ipairs(request._response_headers) do | 
| jbe@264 | 283       http_request:send_header(header[1], header[2]) | 
| jbe@264 | 284     end | 
| jbe@267 | 285     http_request:send_header("Location", encode.url(redirect_data)) | 
| jbe@255 | 286     http_request:finish() | 
| jbe@255 | 287   end | 
| jbe@255 | 288 | 
| jbe@255 | 289   if not success or not redirect_data then | 
| jbe@255 | 290 | 
| jbe@255 | 291     http_request:send_status(request._status or "200 OK") | 
| jbe@255 | 292     for i, header in ipairs(request._response_headers) do | 
| jbe@255 | 293       http_request:send_header(header[1], header[2]) | 
| jbe@255 | 294     end | 
| jbe@291 | 295     if not request._cache_manual then | 
| jbe@291 | 296       local cache_time = request._cache_time | 
| jbe@291 | 297       if request._cache and cache_time and cache_time > 0 then | 
| jbe@291 | 298         http_request:send_header("Cache-Control", "max-age=" .. cache_time) | 
| jbe@291 | 299       else | 
| jbe@291 | 300         http_request:send_header("Cache-Control", "no-cache") | 
| jbe@291 | 301       end | 
| jbe@291 | 302     end | 
| jbe@255 | 303     http_request:send_header("Content-Type", slot.get_content_type()) | 
| jbe@255 | 304     http_request:send_data(slot.render_layout()) | 
| jbe@255 | 305     http_request:finish() | 
| jbe@255 | 306   end | 
| jbe@255 | 307 | 
| jbe@327 | 308   return success | 
| jbe@327 | 309 | 
| jbe@215 | 310 end |