| 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@358 | 7 Called by 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@358 | 70         for element in string.gmatch(subpath, "[^/]+") do | 
| jbe@358 | 71           if element == "." or element == ".." then | 
| jbe@358 | 72             subpath = nil | 
| jbe@358 | 73             break | 
| jbe@358 | 74           end | 
| jbe@358 | 75         end | 
| jbe@347 | 76         local fstat, f, errmsg | 
| jbe@358 | 77         if subpath then | 
| jbe@358 | 78           local filename = WEBMCP_BASE_PATH .. "static/" .. subpath | 
| jbe@358 | 79           fstat, errmsg = extos.stat(filename) | 
| jbe@358 | 80           if fstat then | 
| jbe@358 | 81             if fstat.isdir then | 
| jbe@358 | 82               errmsg = "Is a directory" | 
| jbe@358 | 83             elseif not fstat.isreg then | 
| jbe@358 | 84               errmsg = "Not a regular file" | 
| jbe@358 | 85             else | 
| jbe@358 | 86               f, errmsg = io.open(filename, "r") | 
| jbe@358 | 87             end | 
| jbe@347 | 88           end | 
| jbe@347 | 89         end | 
| jbe@270 | 90         if not f then | 
| jbe@270 | 91           if request.get_404_route() then | 
| jbe@329 | 92             request.set_status("404 Not Found") | 
| jbe@270 | 93             request.forward(request.get_404_route()) | 
| jbe@270 | 94           else | 
| jbe@358 | 95             error('Could not open static file "' .. subpath .. '": ' .. errmsg) | 
| jbe@270 | 96           end | 
| jbe@327 | 97         else | 
| jbe@459 | 98           require_method( | 
| jbe@459 | 99             "Invalid HTTP method for static file", | 
| jbe@459 | 100             "HEAD", "GET", "POST" | 
| jbe@459 | 101           ) | 
| jbe@327 | 102           local d = assert(f:read("*a")) | 
| jbe@327 | 103           f:close() | 
| jbe@327 | 104           slot.put_into("data", d) | 
| jbe@358 | 105           local filename_extension = string.match(subpath, "%.([^.]+)$") | 
| jbe@327 | 106           slot.set_layout(nil, request._mime_types[filename_extension] or "application/octet-stream") | 
| jbe@327 | 107           request.allow_caching() | 
| jbe@327 | 108           return | 
| jbe@270 | 109         end | 
| jbe@255 | 110       end | 
| jbe@255 | 111 | 
| jbe@255 | 112       -- restore slots if coming from http redirect | 
| jbe@327 | 113       do | 
| jbe@327 | 114         local tempstore_value = http_request.get_params["_tempstore"] | 
| jbe@327 | 115         if tempstore_value then | 
| jbe@327 | 116           trace.restore_slots{} | 
| jbe@327 | 117           local blob = tempstore.pop(tempstore_value) | 
| jbe@327 | 118           if blob then slot.restore_all(blob) end | 
| jbe@327 | 119         end | 
| jbe@255 | 120       end | 
| jbe@255 | 121 | 
| jbe@255 | 122       if request.get_action() then | 
| jbe@255 | 123         trace.request{ | 
| jbe@255 | 124           module = request.get_module(), | 
| jbe@255 | 125           action = request.get_action() | 
| jbe@255 | 126         } | 
| jbe@255 | 127         if | 
| jbe@352 | 128           not execute.action{ | 
| jbe@352 | 129             module = request.get_module(), | 
| jbe@352 | 130             action = request.get_action(), | 
| jbe@352 | 131             test_existence = true | 
| jbe@358 | 132           } and | 
| jbe@358 | 133           request.get_404_route() | 
| jbe@255 | 134         then | 
| jbe@255 | 135           request.set_status("404 Not Found") | 
| jbe@255 | 136           request.forward(request.get_404_route()) | 
| jbe@255 | 137         else | 
| jbe@459 | 138           require_method( | 
| jbe@459 | 139             "Invalid HTTP method for action (POST request required)", | 
| jbe@459 | 140             "POST" | 
| jbe@459 | 141           ) | 
| jbe@255 | 142           local action_status = execute.filtered_action{ | 
| jbe@255 | 143             module = request.get_module(), | 
| jbe@255 | 144             action = request.get_action(), | 
| jbe@255 | 145           } | 
| jbe@255 | 146           if not request.is_rerouted() then | 
| jbe@255 | 147             local routing_mode, routing_module, routing_view, routing_anchor | 
| jbe@255 | 148             routing_mode   = http_request.post_params["_webmcp_routing." .. action_status .. ".mode"] | 
| jbe@255 | 149             routing_module = http_request.post_params["_webmcp_routing." .. action_status .. ".module"] | 
| jbe@255 | 150             routing_view   = http_request.post_params["_webmcp_routing." .. action_status .. ".view"] | 
| jbe@255 | 151             routing_anchor = http_request.post_params["_webmcp_routing." .. action_status .. ".anchor"] | 
| jbe@255 | 152             if not (routing_mode or routing_module or routing_view) then | 
| jbe@255 | 153               action_status = "default" | 
| jbe@255 | 154               routing_mode   = http_request.post_params["_webmcp_routing.default.mode"] | 
| jbe@255 | 155               routing_module = http_request.post_params["_webmcp_routing.default.module"] | 
| jbe@255 | 156               routing_view   = http_request.post_params["_webmcp_routing.default.view"] | 
| jbe@255 | 157               routing_anchor = http_request.post_params["_webmcp_routing.default.anchor"] | 
| jbe@255 | 158             end | 
| jbe@255 | 159             assert(routing_module, "Routing information has no module.") | 
| jbe@255 | 160             assert(routing_view,   "Routing information has no view.") | 
| jbe@255 | 161             if routing_mode == "redirect" then | 
| jbe@255 | 162               local routing_params = {} | 
| jbe@255 | 163               for key, value in pairs(request.get_param_strings{ method="POST", include_internal=true }) do | 
| jbe@255 | 164                 local status, stripped_key = string.match( | 
| jbe@255 | 165                   key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$" | 
| jbe@255 | 166                 ) | 
| jbe@255 | 167                 if status == action_status then | 
| jbe@255 | 168                   routing_params[stripped_key] = value | 
| jbe@255 | 169                 end | 
| jbe@255 | 170               end | 
| jbe@255 | 171               request.redirect{ | 
| jbe@255 | 172                 module = routing_module, | 
| jbe@255 | 173                 view   = routing_view, | 
| jbe@255 | 174                 id     = http_request.post_params["_webmcp_routing." .. action_status .. ".id"], | 
| jbe@255 | 175                 params = routing_params, | 
| jbe@255 | 176                 anchor = routing_anchor | 
| jbe@255 | 177               } | 
| jbe@255 | 178             elseif routing_mode == "forward" then | 
| jbe@255 | 179               request.forward{ module = routing_module, view = routing_view } | 
| jbe@255 | 180             else | 
| jbe@255 | 181               error("Missing or unknown routing mode in request parameters.") | 
| jbe@255 | 182             end | 
| jbe@255 | 183           end | 
| jbe@255 | 184         end | 
| jbe@255 | 185       else | 
| jbe@255 | 186         -- no action | 
| jbe@255 | 187         trace.request{ | 
| jbe@255 | 188           module = request.get_module(), | 
| jbe@255 | 189           view   = request.get_view() | 
| jbe@255 | 190         } | 
| jbe@255 | 191         if | 
| jbe@352 | 192           not execute.view{ | 
| jbe@352 | 193             module = request.get_module(), | 
| jbe@352 | 194             view   = request.get_view(), | 
| jbe@352 | 195             test_existence = true | 
| jbe@358 | 196           } and request.get_404_route() | 
| jbe@255 | 197         then | 
| jbe@255 | 198           request.set_status("404 Not Found") | 
| jbe@255 | 199           request.forward(request.get_404_route()) | 
| jbe@255 | 200         end | 
| jbe@255 | 201       end | 
| jbe@255 | 202 | 
| jbe@255 | 203       if not request.get_redirect_data() then | 
| jbe@255 | 204         request.process_forward() | 
| jbe@255 | 205         local view = request.get_view() | 
| jbe@255 | 206         if string.find(view, "^_") then | 
| jbe@255 | 207           error("Tried to call a private view (prefixed with underscore).") | 
| jbe@255 | 208         end | 
| jbe@459 | 209         require_method( | 
| jbe@459 | 210           "Invalid HTTP method", | 
| jbe@459 | 211           "HEAD", "GET", "POST" | 
| jbe@459 | 212         ) | 
| jbe@255 | 213         execute.filtered_view{ | 
| jbe@255 | 214           module = request.get_module(), | 
| jbe@255 | 215           view   = view, | 
| jbe@255 | 216         } | 
| jbe@255 | 217       end | 
| jbe@255 | 218 | 
| jbe@255 | 219     end, | 
| jbe@255 | 220 | 
| jbe@255 | 221     function(errobj) | 
| jbe@255 | 222       return { | 
| jbe@255 | 223         errobj = errobj, | 
| jbe@255 | 224         stacktrace = string.gsub( | 
| jbe@255 | 225           debug.traceback('', 2), | 
| jbe@255 | 226           "^\r?\n?stack traceback:\r?\n?", "" | 
| jbe@255 | 227         ) | 
| jbe@255 | 228       } | 
| jbe@255 | 229     end | 
| jbe@255 | 230   ) | 
| jbe@255 | 231 | 
| jbe@255 | 232   if not success then trace.error{} end | 
| jbe@255 | 233 | 
| jbe@255 | 234   slot.select('trace', trace.render)  -- render trace information | 
| jbe@255 | 235 | 
| jbe@255 | 236   local redirect_data = request.get_redirect_data() | 
| jbe@255 | 237 | 
| jbe@255 | 238   -- log error and switch to error layout, unless success | 
| jbe@255 | 239   if not success then | 
| jbe@255 | 240     local errobj     = error_info.errobj | 
| jbe@255 | 241     local stacktrace = error_info.stacktrace | 
| jbe@255 | 242     if not request._status then | 
| jbe@255 | 243       request._status = "500 Internal Server Error" | 
| jbe@255 | 244     end | 
| jbe@328 | 245     http_request:close_after_finish() | 
| jbe@255 | 246     slot.set_layout('system_error') | 
| jbe@255 | 247     slot.select('system_error', function() | 
| jbe@255 | 248       if getmetatable(errobj) == mondelefant.errorobject_metatable then | 
| jbe@255 | 249         slot.put( | 
| jbe@255 | 250           "<p>Database error of class <b>", | 
| jbe@255 | 251           encode.html(errobj.code), | 
| jbe@255 | 252           "</b> occured:<br/><b>", | 
| jbe@255 | 253           encode.html(errobj.message), | 
| jbe@255 | 254           "</b></p>" | 
| jbe@255 | 255         ) | 
| jbe@255 | 256       else | 
| jbe@255 | 257         slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>") | 
| jbe@255 | 258       end | 
| jbe@255 | 259       slot.put("<p>Stack trace follows:<br/>") | 
| jbe@255 | 260       slot.put(encode.html_newlines(encode.html(stacktrace))) | 
| jbe@255 | 261       slot.put("</p>") | 
| jbe@255 | 262     end) | 
| jbe@255 | 263   elseif redirect_data then | 
| jbe@455 | 264     if | 
| jbe@455 | 265       redirect_data.include_tempstore == true or ( | 
| jbe@455 | 266         redirect_data.include_tempstore ~= false and | 
| jbe@455 | 267         not redirect_data.external | 
| jbe@455 | 268       ) | 
| jbe@455 | 269     then | 
| jbe@455 | 270       redirect_data = table.new(redirect_data) | 
| jbe@454 | 271       redirect_data.params = table.new(redirect_data.params) | 
| jbe@454 | 272       local slot_dump = slot.dump_all() | 
| jbe@454 | 273       if slot_dump ~= "" then | 
| jbe@454 | 274         redirect_data.params._tempstore = tempstore.save(slot_dump) | 
| jbe@454 | 275       end | 
| jbe@255 | 276     end | 
| jbe@255 | 277     http_request:send_status("303 See Other") | 
| jbe@264 | 278     for i, header in ipairs(request._response_headers) do | 
| jbe@264 | 279       http_request:send_header(header[1], header[2]) | 
| jbe@264 | 280     end | 
| jbe@267 | 281     http_request:send_header("Location", encode.url(redirect_data)) | 
| jbe@255 | 282     http_request:finish() | 
| jbe@255 | 283   end | 
| jbe@255 | 284 | 
| jbe@255 | 285   if not success or not redirect_data then | 
| jbe@255 | 286 | 
| jbe@255 | 287     http_request:send_status(request._status or "200 OK") | 
| jbe@255 | 288     for i, header in ipairs(request._response_headers) do | 
| jbe@255 | 289       http_request:send_header(header[1], header[2]) | 
| jbe@255 | 290     end | 
| jbe@291 | 291     if not request._cache_manual then | 
| jbe@291 | 292       local cache_time = request._cache_time | 
| jbe@291 | 293       if request._cache and cache_time and cache_time > 0 then | 
| jbe@291 | 294         http_request:send_header("Cache-Control", "max-age=" .. cache_time) | 
| jbe@291 | 295       else | 
| jbe@291 | 296         http_request:send_header("Cache-Control", "no-cache") | 
| jbe@291 | 297       end | 
| jbe@291 | 298     end | 
| jbe@255 | 299     http_request:send_header("Content-Type", slot.get_content_type()) | 
| jbe@255 | 300     http_request:send_data(slot.render_layout()) | 
| jbe@255 | 301     http_request:finish() | 
| jbe@255 | 302   end | 
| jbe@255 | 303 | 
| jbe@327 | 304   return success | 
| jbe@327 | 305 | 
| jbe@215 | 306 end |