aboutsummaryrefslogtreecommitdiffstats
path: root/net/http/server.lua
diff options
context:
space:
mode:
Diffstat (limited to 'net/http/server.lua')
-rw-r--r--net/http/server.lua283
1 files changed, 283 insertions, 0 deletions
diff --git a/net/http/server.lua b/net/http/server.lua
new file mode 100644
index 00000000..185ac9a0
--- /dev/null
+++ b/net/http/server.lua
@@ -0,0 +1,283 @@
+
+local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
+local parser_new = require "net.http.parser".new;
+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[event]
+ 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"))), 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 ~= b 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 function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
+
+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 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
+ 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;
+ 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)
+ 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;
+ };
+
+ if not request.headers.host then
+ response.status_code = 400;
+ response.headers.content_type = "text/html";
+ response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
+ else
+ -- TODO call handler
+ --response.headers.content_type = "text/plain";
+ --response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
+ 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;
+ 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, fallback to legacy httpserver handlers
+ _M.legacy_handler(request, response);
+ end
+end
+function _M.send_response(response, body)
+ 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 headers.connection == "Keep-Alive" then
+ response:finish_cb();
+ else
+ response.conn:close();
+ end
+end
+function _M.legacy_handler(request, response)
+ log("debug", "Invoking legacy handler");
+ local base = request.path:match("^/([^/?]+)");
+ local legacy_server = legacy_httpserver and legacy_httpserver.new.http_servers[5280];
+ local handler = legacy_server and legacy_server.handlers[base];
+ if not handler then handler = legacy_httpserver and legacy_httpserver.set_default_handler.default_handler; end
+ if handler then
+ -- add legacy properties to request object
+ request.url = { path = request.path };
+ request.handler = response.conn;
+ request.id = tostring{}:match("%x+$");
+ local headers = {};
+ for k,v in pairs(request.headers) do
+ headers[k:gsub("_", "-")] = v;
+ end
+ request.headers = headers;
+ function request:send(resp)
+ if self.destroyed then return; end
+ if resp.body or resp.headers then
+ if resp.headers then
+ for k,v in pairs(resp.headers) do response.headers[k] = v; end
+ end
+ response:send(resp.body)
+ else
+ response:send(resp)
+ end
+ self.sent = true;
+ self:destroy();
+ end
+ function request:destroy()
+ if self.destroyed then return; end
+ if not self.sent then return self:send(""); end
+ self.destroyed = true;
+ if self.on_destroy then
+ log("debug", "Request has destroy callback");
+ self:on_destroy();
+ else
+ log("debug", "Request has no destroy callback");
+ end
+ end
+ local r = handler(request.method, request.body, request);
+ if r ~= true then
+ request:send(r);
+ end
+ else
+ log("debug", "No handler found");
+ response.status_code = 404;
+ response.headers.content_type = "text/html";
+ response:send("<html><head><title>404 Not Found</title></head><body>404 Not Found: No such page.</body></html>");
+ 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;
+return _M;