978a5a820076c8d68c792e26279be059dc504510
[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
22 local listener = {};
23
24 local function is_wildcard_event(event)
25         return event:sub(-2, -1) == "/*";
26 end
27 local function is_wildcard_match(wildcard_event, event)
28         return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
29 end
30
31 local event_map = events._event_map;
32 setmetatable(events._handlers, {
33         __index = function (handlers, curr_event)
34                 if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired
35                 -- Find all handlers that could match this event, sort them
36                 -- and then put the array into handlers[curr_event] (and return it)
37                 local matching_handlers_set = {};
38                 local handlers_array = {};
39                 for event, handlers_set in pairs(event_map) do
40                         if event == curr_event or
41                         is_wildcard_event(event) and is_wildcard_match(event, curr_event) then
42                                 for handler, priority in pairs(handlers_set) do
43                                         matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority };
44                                         table.insert(handlers_array, handler);
45                                 end
46                         end
47                 end
48                 if #handlers_array > 0 then
49                         table.sort(handlers_array, function(b, a)
50                                 local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b];
51                                 for i = 1, #a_score do
52                                         if a_score[i] ~= b_score[i] then -- If equal, compare next score value
53                                                 return a_score[i] < b_score[i];
54                                         end
55                                 end
56                                 return false;
57                         end);
58                 else
59                         handlers_array = false;
60                 end
61                 rawset(handlers, curr_event, handlers_array);
62                 return handlers_array;
63         end;
64         __newindex = function (handlers, curr_event, handlers_array)
65                 if handlers_array == nil
66                 and is_wildcard_event(curr_event) then
67                         -- Invalidate the indexes of all matching events
68                         for event in pairs(handlers) do
69                                 if is_wildcard_match(curr_event, event) then
70                                         handlers[event] = nil;
71                                 end
72                         end
73                 end
74                 rawset(handlers, curr_event, handlers_array);
75         end;
76 });
77
78 local handle_request;
79 local _1, _2, _3;
80 local function _handle_request() return handle_request(_1, _2, _3); end
81
82 local last_err;
83 local function _traceback_handler(err) last_err = err; log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
84 events.add_handler("http-error", function (error)
85         return "Error processing request: "..codes[error.code]..". Check your error log for more information.";
86 end, -1);
87
88 function listener.onconnect(conn)
89         local secure = conn:ssl() and true or nil;
90         local pending = {};
91         local waiting = false;
92         local function process_next(last_response)
93                 --if waiting then log("debug", "can't process_next, waiting"); return; end
94                 if sessions[conn] and #pending > 0 then
95                         local request = t_remove(pending);
96                         --log("debug", "process_next: %s", request.path);
97                         waiting = true;
98                         --handle_request(conn, request, process_next);
99                         _1, _2, _3 = conn, request, process_next;
100                         if not xpcall(_handle_request, _traceback_handler) then
101                                 conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err }));
102                                 conn:close();
103                         end
104                 else
105                         --log("debug", "ready for more");
106                         waiting = false;
107                 end
108         end
109         local function success_cb(request)
110                 --log("debug", "success_cb: %s", request.path);
111                 request.secure = secure;
112                 t_insert(pending, request);
113                 if not waiting then
114                         process_next();
115                 end
116         end
117         local function error_cb(err)
118                 log("debug", "error_cb: %s", err or "<nil>");
119                 -- FIXME don't close immediately, wait until we process current stuff
120                 -- FIXME if err, send off a bad-request response
121                 sessions[conn] = nil;
122                 conn:close();
123         end
124         sessions[conn] = parser_new(success_cb, error_cb);
125 end
126
127 function listener.ondisconnect(conn)
128         local open_response = conn._http_open_response;
129         if open_response and open_response.on_destroy then
130                 open_response.finished = true;
131                 open_response:on_destroy();
132         end
133         sessions[conn] = nil;
134 end
135
136 function listener.onincoming(conn, data)
137         sessions[conn]:feed(data);
138 end
139
140 local headerfix = setmetatable({}, {
141         __index = function(t, k)
142                 local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
143                 t[k] = v;
144                 return v;
145         end
146 });
147
148 function _M.hijack_response(response, listener)
149         error("TODO");
150 end
151 function handle_request(conn, request, finish_cb)
152         --log("debug", "handler: %s", request.path);
153         local headers = {};
154         for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
155         request.headers = headers;
156         request.conn = conn;
157
158         local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
159         local conn_header = request.headers.connection;
160         local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
161
162         local response = {
163                 request = request;
164                 status_code = 200;
165                 headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
166                 conn = conn;
167                 send = _M.send_response;
168                 finish_cb = finish_cb;
169         };
170         conn._http_open_response = response;
171
172         local err;
173         if not request.headers.host then
174                 err = "No 'Host' header";
175         elseif not request.path then
176                 err = "Invalid path";
177         end
178         
179         if err then
180                 response.status_code = 400;
181                 response.headers.content_type = "text/html";
182                 response:send(events.fire_event("http-error", { code = 400, message = err }));
183         else
184                 local host = request.headers.host;
185                 if host then
186                         host = host:match("[^:]*"):lower();
187                         local event = request.method.." "..host..request.path:match("[^?]*");
188                         local payload = { request = request, response = response };
189                         --log("debug", "Firing event: %s", event);
190                         local result = events.fire_event(event, payload);
191                         if result ~= nil then
192                                 if result ~= true then
193                                         local code, body = 200, "";
194                                         local result_type = type(result);
195                                         if result_type == "number" then
196                                                 response.status_code = result;
197                                                 if result >= 400 then
198                                                         body = events.fire_event("http-error", { code = result });
199                                                 end
200                                         elseif result_type == "string" then
201                                                 body = result;
202                                         elseif result_type == "table" then
203                                                 body = result.body;
204                                                 result.body = nil;
205                                                 for k, v in pairs(result) do
206                                                         response[k] = v;
207                                                 end
208                                         end
209                                         response:send(body);
210                                 end
211                                 return;
212                         end
213                 end
214
215                 -- if handler not called, return 404
216                 response.status_code = 404;
217                 response.headers.content_type = "text/html";
218                 response:send(events.fire_event("http-error", { code = 404 }));
219         end
220 end
221 function _M.send_response(response, body)
222         if response.finished then return; end
223         response.finished = true;
224         response.conn._http_open_response = nil;
225         
226         local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
227         local headers = response.headers;
228         body = body or "";
229         headers.content_length = #body;
230
231         local output = { status_line };
232         for k,v in pairs(headers) do
233                 t_insert(output, headerfix[k]..v);
234         end
235         t_insert(output, "\r\n\r\n");
236         t_insert(output, body);
237
238         response.conn:write(t_concat(output));
239         if response.on_destroy then
240                 response:on_destroy();
241                 response.on_destroy = nil;
242         end
243         if headers.connection == "Keep-Alive" then
244                 response:finish_cb();
245         else
246                 response.conn:close();
247         end
248 end
249 function _M.add_handler(event, handler, priority)
250         events.add_handler(event, handler, priority);
251 end
252 function _M.remove_handler(event, handler)
253         events.remove_handler(event, handler);
254 end
255
256 function _M.listen_on(port, interface, ssl)
257         addserver(interface or "*", port, listener, "*a", ssl);
258 end
259
260 _M.listener = listener;
261 _M.codes = codes;
262 _M._events = events;
263 return _M;