From 06f2b0f3a44b915c20105405ddf3e550bdf25233 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sun, 8 Apr 2012 04:09:33 +0500 Subject: net.http.{server|codes|parser}: Initial commit. --- net/http/server.lua | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 net/http/server.lua (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua new file mode 100644 index 00000000..788f046b --- /dev/null +++ b/net/http/server.lua @@ -0,0 +1,223 @@ + +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 _M = {}; + +local sessions = {}; +local handlers = {}; + +local listener = {}; + +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 ""); + -- 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("400 Bad Request400 Bad Request: No Host header."); + 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 }; + --[[repeat + if events.fire_event(event, payload) ~= nil then return; end + event = (event:sub(-1) == "/") and event:sub(1, -1) or event:gsub("[^/]*$", ""); + if event:sub(-1) == "/" then + event = event:sub(1, -1); + else + event = event:gsub("[^/]*$", ""); + end + until not event:find("/", 1, true);]] + --log("debug", "Event: %s", event); + if events.fire_event(event, payload) ~= nil then return; end + -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect + 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 = _G.httpserver and _G.httpserver.new.http_servers[5280]; + local handler = legacy_server and legacy_server.handlers[base]; + if not handler then handler = _G.httpserver and _G.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("404 Not Found404 Not Found: No such page."); + 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; -- cgit v1.2.3 From becd794524489f5888069d1dc6612600e7d7a80e Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sat, 21 Apr 2012 17:37:00 +0500 Subject: net.http.server: Missing in 404 Not Found response. --- net/http/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index 788f046b..feb8f766 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -203,7 +203,7 @@ function _M.legacy_handler(request, response) log("debug", "No handler found"); response.status_code = 404; response.headers.content_type = "text/html"; - response:send("<html><head>404 Not Found</head><body>404 Not Found: No such page.</body></html>"); + response:send("<html><head><title>404 Not Found404 Not Found: No such page."); end end -- cgit v1.2.3 From 59bd215bb29f37b2bbf809728f1d894a9ce112a4 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sat, 21 Apr 2012 17:38:01 +0500 Subject: net.http.server: Fire global HTTP event when no specific handlers available. --- net/http/server.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index feb8f766..1f61c7b9 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -129,6 +129,7 @@ function handle_request(conn, request, finish_cb) --log("debug", "Event: %s", event); if events.fire_event(event, payload) ~= nil then return; end -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect + if events.fire_event("*", payload) ~= nil then return; end end -- if handler not called, fallback to legacy httpserver handlers -- cgit v1.2.3 From c065013cedb83885226e5870a34cf4c2add53450 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sun, 22 Apr 2012 23:44:21 +0500 Subject: net.http.server: Fix legacy net.httpserver fallback (httpserver is no longer a global). --- net/http/server.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index 1f61c7b9..3a0cb53a 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -13,6 +13,7 @@ local debug = debug; local tostring = tostring; local codes = require "net.http.codes"; local _G = _G; +local legacy_httpserver = require "net.httpserver"; local _M = {}; @@ -159,9 +160,9 @@ end function _M.legacy_handler(request, response) log("debug", "Invoking legacy handler"); local base = request.path:match("^/([^/?]+)"); - local legacy_server = _G.httpserver and _G.httpserver.new.http_servers[5280]; + 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 = _G.httpserver and _G.httpserver.set_default_handler.default_handler; end + 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 }; -- cgit v1.2.3 From f2b41269670e6947a2f43c3d793cac9b56bccf02 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 23 Apr 2012 21:29:18 +0100 Subject: net.http.server: Support for wildcard events (events that end with '/*') --- net/http/server.lua | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index 3a0cb53a..94487e5e 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -22,6 +22,57 @@ local handlers = {}; local listener = {}; +local function is_wildcard_event(event) + return event:sub(-2, -1) == "/*"; +end +local function is_wildcard_match(wildcard_event, event) + log("debug", "comparing %q with %q", wildcard_event:sub(1, -2), event:sub(1, #wildcard_event-1)); + 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 -- cgit v1.2.3 From 37f43d0189e9702406bfaf97e8b616f1dd783648 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 24 Apr 2012 19:05:45 +0100 Subject: net.http.server: Remove debug message --- net/http/server.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index 94487e5e..50a5c5a1 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -26,7 +26,6 @@ local function is_wildcard_event(event) return event:sub(-2, -1) == "/*"; end local function is_wildcard_match(wildcard_event, event) - log("debug", "comparing %q with %q", wildcard_event:sub(1, -2), event:sub(1, #wildcard_event-1)); return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1); end -- cgit v1.2.3 From 7cb22f77a78741a1bd085c6b3dc3b31f51c1fa09 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 24 Apr 2012 19:07:12 +0100 Subject: net.http.server: Handle results returned by handlers, and send as a response. Also removes explicit firing of '*', which can now be done via wildcard events. --- net/http/server.lua | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'net/http/server.lua') diff --git a/net/http/server.lua b/net/http/server.lua index 50a5c5a1..185ac9a0 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -168,19 +168,27 @@ function handle_request(conn, request, finish_cb) host = host:match("[^:]*"):lower(); local event = request.method.." "..host..request.path:match("[^?]*"); local payload = { request = request, response = response }; - --[[repeat - if events.fire_event(event, payload) ~= nil then return; end - event = (event:sub(-1) == "/") and event:sub(1, -1) or event:gsub("[^/]*$", ""); - if event:sub(-1) == "/" then - event = event:sub(1, -1); - else - event = event:gsub("[^/]*$", ""); + --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 - until not event:find("/", 1, true);]] - --log("debug", "Event: %s", event); - if events.fire_event(event, payload) ~= nil then return; end - -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect - if events.fire_event("*", payload) ~= nil then return; end + return; + end end -- if handler not called, fallback to legacy httpserver handlers -- cgit v1.2.3