94487e5e566786ae20f9b41890e63ec45109c529
[prosody.git] / net / http / server.lua
1
2 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
3 local parser_new = require "net.http.parser".new;
4 local events = require "util.events".new();
5 local addserver = require "net.server".addserver;
6 local log = require "util.logger".init("http.server");
7 local os_date = os.date;
8 local pairs = pairs;
9 local s_upper = string.upper;
10 local setmetatable = setmetatable;
11 local xpcall = xpcall;
12 local debug = debug;
13 local tostring = tostring;
14 local codes = require "net.http.codes";
15 local _G = _G;
16 local legacy_httpserver = require "net.httpserver";
17
18 local _M = {};
19
20 local sessions = {};
21 local handlers = {};
22
23 local listener = {};
24
25 local function is_wildcard_event(event)
26         return event:sub(-2, -1) == "/*";
27 end
28 local function is_wildcard_match(wildcard_event, event)
29         log("debug", "comparing %q with %q", wildcard_event:sub(1, -2), event:sub(1, #wildcard_event-1));
30         return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
31 end
32
33 local event_map = events._event_map;
34 setmetatable(events._handlers, {
35         __index = function (handlers, curr_event)
36                 if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired
37                 -- Find all handlers that could match this event, sort them
38                 -- and then put the array into handlers[event]
39                 local matching_handlers_set = {};
40                 local handlers_array = {};
41                 for event, handlers_set in pairs(event_map) do
42                         if event == curr_event or
43                         is_wildcard_event(event) and is_wildcard_match(event, curr_event) then
44                                 for handler, priority in pairs(handlers_set) do
45                                         matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), priority };
46                                         table.insert(handlers_array, handler);
47                                 end
48                         end
49                 end
50                 if #handlers_array == 0 then return; end
51                 table.sort(handlers_array, function(b, a)
52                         local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b];
53                         for i = 1, #a_score do
54                                 if a ~= b then -- If equal, compare next score value
55                                         return a_score[i] < b_score[i];
56                                 end
57                         end
58                         return false;
59                 end);
60                 handlers[curr_event] = handlers_array;
61                 return handlers_array;
62         end;
63         __newindex = function (handlers, curr_event, handlers_array)
64                 if handlers_array == nil
65                 and is_wildcard_event(curr_event) then
66                         -- Invalidate all matching
67                         for event in pairs(handlers) do
68                                 if is_wildcard_match(curr_event, event) then
69                                         handlers[event] = nil;
70                                 end
71                         end
72                 end
73         end;
74 });
75
76 local handle_request;
77 local _1, _2, _3;
78 local function _handle_request() return handle_request(_1, _2, _3); end
79 local function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
80
81 function listener.onconnect(conn)
82         local secure = conn:ssl() and true or nil;
83         local pending = {};
84         local waiting = false;
85         local function process_next(last_response)
86                 --if waiting then log("debug", "can't process_next, waiting"); return; end
87                 if sessions[conn] and #pending > 0 then
88                         local request = t_remove(pending);
89                         --log("debug", "process_next: %s", request.path);
90                         waiting = true;
91                         --handle_request(conn, request, process_next);
92                         _1, _2, _3 = conn, request, process_next;
93                         if not xpcall(_handle_request, _traceback_handler) then
94                                 conn:write("HTTP/1.0 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
95                                 conn:close();
96                         end
97                 else
98                         --log("debug", "ready for more");
99                         waiting = false;
100                 end
101         end
102         local function success_cb(request)
103                 --log("debug", "success_cb: %s", request.path);
104                 request.secure = secure;
105                 t_insert(pending, request);
106                 if not waiting then
107                         process_next();
108                 end
109         end
110         local function error_cb(err)
111                 log("debug", "error_cb: %s", err or "<nil>");
112                 -- FIXME don't close immediately, wait until we process current stuff
113                 -- FIXME if err, send off a bad-request response
114                 sessions[conn] = nil;
115                 conn:close();
116         end
117         sessions[conn] = parser_new(success_cb, error_cb);
118 end
119
120 function listener.ondisconnect(conn)
121         sessions[conn] = nil;
122 end
123
124 function listener.onincoming(conn, data)
125         sessions[conn]:feed(data);
126 end
127
128 local headerfix = setmetatable({}, {
129         __index = function(t, k)
130                 local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
131                 t[k] = v;
132                 return v;
133         end
134 });
135
136 function _M.hijack_response(response, listener)
137         error("TODO");
138 end
139 function handle_request(conn, request, finish_cb)
140         --log("debug", "handler: %s", request.path);
141         local headers = {};
142         for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
143         request.headers = headers;
144         request.conn = conn;
145
146         local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
147         local conn_header = request.headers.connection;
148         local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
149
150         local response = {
151                 request = request;
152                 status_code = 200;
153                 headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
154                 conn = conn;
155                 send = _M.send_response;
156                 finish_cb = finish_cb;
157         };
158
159         if not request.headers.host then
160                 response.status_code = 400;
161                 response.headers.content_type = "text/html";
162                 response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
163         else
164                 -- TODO call handler
165                 --response.headers.content_type = "text/plain";
166                 --response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
167                 local host = request.headers.host;
168                 if host then
169                         host = host:match("[^:]*"):lower();
170                         local event = request.method.." "..host..request.path:match("[^?]*");
171                         local payload = { request = request, response = response };
172                         --[[repeat
173                                 if events.fire_event(event, payload) ~= nil then return; end
174                                 event = (event:sub(-1) == "/") and event:sub(1, -1) or event:gsub("[^/]*$", "");
175                                 if event:sub(-1) == "/" then
176                                         event = event:sub(1, -1);
177                                 else
178                                         event = event:gsub("[^/]*$", "");
179                                 end
180                         until not event:find("/", 1, true);]]
181                         --log("debug", "Event: %s", event);
182                         if events.fire_event(event, payload) ~= nil then return; end
183                         -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect
184                         if events.fire_event("*", payload) ~= nil then return; end
185                 end
186
187                 -- if handler not called, fallback to legacy httpserver handlers
188                 _M.legacy_handler(request, response);
189         end
190 end
191 function _M.send_response(response, body)
192         local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
193         local headers = response.headers;
194         body = body or "";
195         headers.content_length = #body;
196
197         local output = { status_line };
198         for k,v in pairs(headers) do
199                 t_insert(output, headerfix[k]..v);
200         end
201         t_insert(output, "\r\n\r\n");
202         t_insert(output, body);
203
204         response.conn:write(t_concat(output));
205         if headers.connection == "Keep-Alive" then
206                 response:finish_cb();
207         else
208                 response.conn:close();
209         end
210 end
211 function _M.legacy_handler(request, response)
212         log("debug", "Invoking legacy handler");
213         local base = request.path:match("^/([^/?]+)");
214         local legacy_server = legacy_httpserver and legacy_httpserver.new.http_servers[5280];
215         local handler = legacy_server and legacy_server.handlers[base];
216         if not handler then handler = legacy_httpserver and legacy_httpserver.set_default_handler.default_handler; end
217         if handler then
218                 -- add legacy properties to request object
219                 request.url = { path = request.path };
220                 request.handler = response.conn;
221                 request.id = tostring{}:match("%x+$");
222                 local headers = {};
223                 for k,v in pairs(request.headers) do
224                         headers[k:gsub("_", "-")] = v;
225                 end
226                 request.headers = headers;
227                 function request:send(resp)
228                         if self.destroyed then return; end
229                         if resp.body or resp.headers then
230                                 if resp.headers then
231                                         for k,v in pairs(resp.headers) do response.headers[k] = v; end
232                                 end
233                                 response:send(resp.body)
234                         else
235                                 response:send(resp)
236                         end
237                         self.sent = true;
238                         self:destroy();
239                 end
240                 function request:destroy()
241                         if self.destroyed then return; end
242                         if not self.sent then return self:send(""); end
243                         self.destroyed = true;
244                         if self.on_destroy then
245                                 log("debug", "Request has destroy callback");
246                                 self:on_destroy();
247                         else
248                                 log("debug", "Request has no destroy callback");
249                         end
250                 end
251                 local r = handler(request.method, request.body, request);
252                 if r ~= true then
253                         request:send(r);
254                 end
255         else
256                 log("debug", "No handler found");
257                 response.status_code = 404;
258                 response.headers.content_type = "text/html";
259                 response:send("<html><head><title>404 Not Found</title></head><body>404 Not Found: No such page.</body></html>");
260         end
261 end
262
263 function _M.add_handler(event, handler, priority)
264         events.add_handler(event, handler, priority);
265 end
266 function _M.remove_handler(event, handler)
267         events.remove_handler(event, handler);
268 end
269
270 function _M.listen_on(port, interface, ssl)
271         addserver(interface or "*", port, listener, "*a", ssl);
272 end
273
274 _M.listener = listener;
275 _M.codes = codes;
276 return _M;