IMPORTANT: due to a drive failure, as of 13-Mar-2021, the Mercurial repository had to be re-mirrored, which changed every commit SHA. The old SHAs and trees are backed up in the vault branches. Please migrate to the new branches as soon as you can.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
prosody/net/http/server.lua

258 lines
7.8 KiB

local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
local parser_new = require "net.http.parser".new;
local url_parse = require "socket.url".parse;
local events = require "util.events".new();
local addserver = require "net.server".addserver;
local log = require "util.logger".init("http.server");
local os_date = os.date;
local pairs = pairs;
local s_upper = string.upper;
local setmetatable = setmetatable;
local xpcall = xpcall;
local debug = debug;
local tostring = tostring;
local codes = require "net.http.codes";
local _G = _G;
local legacy_httpserver = require "net.httpserver";
local _M = {};
local sessions = {};
local handlers = {};
local listener = {};
local function is_wildcard_event(event)
return event:sub(-2, -1) == "/*";
end
local function is_wildcard_match(wildcard_event, event)
return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
end
local event_map = events._event_map;
setmetatable(events._handlers, {
__index = function (handlers, curr_event)
if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired
-- Find all handlers that could match this event, sort them
-- and then put the array into handlers[curr_event] (and return it)
local matching_handlers_set = {};
local handlers_array = {};
for event, handlers_set in pairs(event_map) do
if event == curr_event or
is_wildcard_event(event) and is_wildcard_match(event, curr_event) then
for handler, priority in pairs(handlers_set) do
matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority };
table.insert(handlers_array, handler);
end
end
end
if #handlers_array == 0 then return; end
table.sort(handlers_array, function(b, a)
local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b];
for i = 1, #a_score do
if a_score[i] ~= b_score[i] then -- If equal, compare next score value
return a_score[i] < b_score[i];
end
end
return false;
end);
handlers[curr_event] = handlers_array;
return handlers_array;
end;
__newindex = function (handlers, curr_event, handlers_array)
if handlers_array == nil
and is_wildcard_event(curr_event) then
-- Invalidate all matching
for event in pairs(handlers) do
if is_wildcard_match(curr_event, event) then
handlers[event] = nil;
end
end
end
end;
});
local handle_request;
local _1, _2, _3;
local function _handle_request() return handle_request(_1, _2, _3); end
local last_err;
local function _traceback_handler(err) last_err = err; log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
events.add_handler("http-error", function (error)
return "Error processing request: "..codes[error.code]..". Check your error log for more information.";
end, -1);
function listener.onconnect(conn)
local secure = conn:ssl() and true or nil;
local pending = {};
local waiting = false;
local function process_next(last_response)
--if waiting then log("debug", "can't process_next, waiting"); return; end
if sessions[conn] and #pending > 0 then
local request = t_remove(pending);
--log("debug", "process_next: %s", request.path);
waiting = true;
--handle_request(conn, request, process_next);
_1, _2, _3 = conn, request, process_next;
if not xpcall(_handle_request, _traceback_handler) then
conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err }));
conn:close();
end
else
--log("debug", "ready for more");
waiting = false;
end
end
local function success_cb(request)
--log("debug", "success_cb: %s", request.path);
request.secure = secure;
local parsed_dest = url_parse(request.path);
request.url = parsed_dest;
request.path = parsed_dest.path;
t_insert(pending, request);
if not waiting then
process_next();
end
end
local function error_cb(err)
log("debug", "error_cb: %s", err or "<nil>");
-- FIXME don't close immediately, wait until we process current stuff
-- FIXME if err, send off a bad-request response
sessions[conn] = nil;
conn:close();
end
sessions[conn] = parser_new(success_cb, error_cb);
end
function listener.ondisconnect(conn)
local open_response = conn._http_open_response;
if open_response and open_response.on_destroy then
open_response.finished = true;
open_response:on_destroy();
end
sessions[conn] = nil;
end
function listener.onincoming(conn, data)
sessions[conn]:feed(data);
end
local headerfix = setmetatable({}, {
__index = function(t, k)
local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
t[k] = v;
return v;
end
});
function _M.hijack_response(response, listener)
error("TODO");
end
function handle_request(conn, request, finish_cb)
--log("debug", "handler: %s", request.path);
local headers = {};
for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
request.headers = headers;
request.conn = conn;
local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
local conn_header = request.headers.connection;
local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
local response = {
request = request;
status_code = 200;
headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
conn = conn;
send = _M.send_response;
finish_cb = finish_cb;
};
conn._http_open_response = response;
if not request.headers.host then
response.status_code = 400;
response.headers.content_type = "text/html";
response:send(events.fire_event("http-error", { code = 400, message = "No 'Host' header" }));
else
local host = request.headers.host;
if host then
host = host:match("[^:]*"):lower();
local event = request.method.." "..host..request.path:match("[^?]*");
local payload = { request = request, response = response };
--log("debug", "Firing event: %s", event);
local result = events.fire_event(event, payload);
if result ~= nil then
if result ~= true then
local code, body = 200, "";
local result_type = type(result);
if result_type == "number" then
response.status_code = result;
if result >= 400 then
body = events.fire_event("http-error", { code = result });
end
elseif result_type == "string" then
body = result;
elseif result_type == "table" then
body = result.body;
result.body = nil;
for k, v in pairs(result) do
response[k] = v;
end
end
response:send(body);
end
return;
end
end
-- if handler not called, return 404
response.status_code = 404;
response.headers.content_type = "text/html";
response:send(events.fire_event("http-error", { code = 404 }));
end
end
function _M.send_response(response, body)
if response.finished then return; end
response.finished = true;
response.conn._http_open_response = nil;
local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
local headers = response.headers;
body = body or "";
headers.content_length = #body;
local output = { status_line };
for k,v in pairs(headers) do
t_insert(output, headerfix[k]..v);
end
t_insert(output, "\r\n\r\n");
t_insert(output, body);
response.conn:write(t_concat(output));
if response.on_destroy then
response:on_destroy();
response.on_destroy = nil;
end
if headers.connection == "Keep-Alive" then
response:finish_cb();
else
response.conn:close();
end
end
function _M.add_handler(event, handler, priority)
events.add_handler(event, handler, priority);
end
function _M.remove_handler(event, handler)
events.remove_handler(event, handler);
end
function _M.listen_on(port, interface, ssl)
addserver(interface or "*", port, listener, "*a", ssl);
end
_M.listener = listener;
_M.codes = codes;
_M._events = events;
return _M;