net.http.server: Missing <title/> in 404 Not Found response.
[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
17 local _M = {};
18
19 local sessions = {};
20 local handlers = {};
21
22 local listener = {};
23
24 local handle_request;
25 local _1, _2, _3;
26 local function _handle_request() return handle_request(_1, _2, _3); end
27 local function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
28
29 function listener.onconnect(conn)
30         local secure = conn:ssl() and true or nil;
31         local pending = {};
32         local waiting = false;
33         local function process_next(last_response)
34                 --if waiting then log("debug", "can't process_next, waiting"); return; end
35                 if sessions[conn] and #pending > 0 then
36                         local request = t_remove(pending);
37                         --log("debug", "process_next: %s", request.path);
38                         waiting = true;
39                         --handle_request(conn, request, process_next);
40                         _1, _2, _3 = conn, request, process_next;
41                         if not xpcall(_handle_request, _traceback_handler) then
42                                 conn:write("HTTP/1.0 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
43                                 conn:close();
44                         end
45                 else
46                         --log("debug", "ready for more");
47                         waiting = false;
48                 end
49         end
50         local function success_cb(request)
51                 --log("debug", "success_cb: %s", request.path);
52                 request.secure = secure;
53                 t_insert(pending, request);
54                 if not waiting then
55                         process_next();
56                 end
57         end
58         local function error_cb(err)
59                 log("debug", "error_cb: %s", err or "<nil>");
60                 -- FIXME don't close immediately, wait until we process current stuff
61                 -- FIXME if err, send off a bad-request response
62                 sessions[conn] = nil;
63                 conn:close();
64         end
65         sessions[conn] = parser_new(success_cb, error_cb);
66 end
67
68 function listener.ondisconnect(conn)
69         sessions[conn] = nil;
70 end
71
72 function listener.onincoming(conn, data)
73         sessions[conn]:feed(data);
74 end
75
76 local headerfix = setmetatable({}, {
77         __index = function(t, k)
78                 local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
79                 t[k] = v;
80                 return v;
81         end
82 });
83
84 function _M.hijack_response(response, listener)
85         error("TODO");
86 end
87 function handle_request(conn, request, finish_cb)
88         --log("debug", "handler: %s", request.path);
89         local headers = {};
90         for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
91         request.headers = headers;
92         request.conn = conn;
93
94         local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
95         local conn_header = request.headers.connection;
96         local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
97
98         local response = {
99                 request = request;
100                 status_code = 200;
101                 headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
102                 conn = conn;
103                 send = _M.send_response;
104                 finish_cb = finish_cb;
105         };
106
107         if not request.headers.host then
108                 response.status_code = 400;
109                 response.headers.content_type = "text/html";
110                 response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
111         else
112                 -- TODO call handler
113                 --response.headers.content_type = "text/plain";
114                 --response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
115                 local host = request.headers.host;
116                 if host then
117                         host = host:match("[^:]*"):lower();
118                         local event = request.method.." "..host..request.path:match("[^?]*");
119                         local payload = { request = request, response = response };
120                         --[[repeat
121                                 if events.fire_event(event, payload) ~= nil then return; end
122                                 event = (event:sub(-1) == "/") and event:sub(1, -1) or event:gsub("[^/]*$", "");
123                                 if event:sub(-1) == "/" then
124                                         event = event:sub(1, -1);
125                                 else
126                                         event = event:gsub("[^/]*$", "");
127                                 end
128                         until not event:find("/", 1, true);]]
129                         --log("debug", "Event: %s", event);
130                         if events.fire_event(event, payload) ~= nil then return; end
131                         -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect
132                 end
133
134                 -- if handler not called, fallback to legacy httpserver handlers
135                 _M.legacy_handler(request, response);
136         end
137 end
138 function _M.send_response(response, body)
139         local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
140         local headers = response.headers;
141         body = body or "";
142         headers.content_length = #body;
143
144         local output = { status_line };
145         for k,v in pairs(headers) do
146                 t_insert(output, headerfix[k]..v);
147         end
148         t_insert(output, "\r\n\r\n");
149         t_insert(output, body);
150
151         response.conn:write(t_concat(output));
152         if headers.connection == "Keep-Alive" then
153                 response:finish_cb();
154         else
155                 response.conn:close();
156         end
157 end
158 function _M.legacy_handler(request, response)
159         log("debug", "Invoking legacy handler");
160         local base = request.path:match("^/([^/?]+)");
161         local legacy_server = _G.httpserver and _G.httpserver.new.http_servers[5280];
162         local handler = legacy_server and legacy_server.handlers[base];
163         if not handler then handler = _G.httpserver and _G.httpserver.set_default_handler.default_handler; end
164         if handler then
165                 -- add legacy properties to request object
166                 request.url = { path = request.path };
167                 request.handler = response.conn;
168                 request.id = tostring{}:match("%x+$");
169                 local headers = {};
170                 for k,v in pairs(request.headers) do
171                         headers[k:gsub("_", "-")] = v;
172                 end
173                 request.headers = headers;
174                 function request:send(resp)
175                         if self.destroyed then return; end
176                         if resp.body or resp.headers then
177                                 if resp.headers then
178                                         for k,v in pairs(resp.headers) do response.headers[k] = v; end
179                                 end
180                                 response:send(resp.body)
181                         else
182                                 response:send(resp)
183                         end
184                         self.sent = true;
185                         self:destroy();
186                 end
187                 function request:destroy()
188                         if self.destroyed then return; end
189                         if not self.sent then return self:send(""); end
190                         self.destroyed = true;
191                         if self.on_destroy then
192                                 log("debug", "Request has destroy callback");
193                                 self:on_destroy();
194                         else
195                                 log("debug", "Request has no destroy callback");
196                         end
197                 end
198                 local r = handler(request.method, request.body, request);
199                 if r ~= true then
200                         request:send(r);
201                 end
202         else
203                 log("debug", "No handler found");
204                 response.status_code = 404;
205                 response.headers.content_type = "text/html";
206                 response:send("<html><head><title>404 Not Found</title></head><body>404 Not Found: No such page.</body></html>");
207         end
208 end
209
210 function _M.add_handler(event, handler, priority)
211         events.add_handler(event, handler, priority);
212 end
213 function _M.remove_handler(event, handler)
214         events.remove_handler(event, handler);
215 end
216
217 function _M.listen_on(port, interface, ssl)
218         addserver(interface or "*", port, listener, "*a", ssl);
219 end
220
221 _M.listener = listener;
222 _M.codes = codes;
223 return _M;