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