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