diff options
Diffstat (limited to 'net/http/server.lua')
-rw-r--r-- | net/http/server.lua | 172 |
1 files changed, 111 insertions, 61 deletions
diff --git a/net/http/server.lua b/net/http/server.lua index 3873bbe0..ab71dbc9 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -1,5 +1,5 @@ -local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; +local t_insert, t_concat = table.insert, table.concat; local parser_new = require "net.http.parser".new; local events = require "util.events".new(); local addserver = require "net.server".addserver; @@ -8,12 +8,12 @@ local os_date = os.date; local pairs = pairs; local s_upper = string.upper; local setmetatable = setmetatable; -local xpcall = require "util.xpcall".xpcall; -local traceback = debug.traceback; -local tostring = tostring; local cache = require "util.cache"; local codes = require "net.http.codes"; +local promise = require "util.promise"; +local errors = require "util.error"; local blocksize = 2^16; +local async = require "util.async"; local _M = {}; @@ -89,51 +89,60 @@ setmetatable(events._handlers, { local handle_request; -local last_err; -local function _traceback_handler(err) last_err = err; log("error", "Traceback[httpserver]: %s", traceback(tostring(err), 2)); end events.add_handler("http-error", function (error) return "Error processing request: "..codes[error.code]..". Check your error log for more information."; end, -1); +local runner_callbacks = {}; + +function runner_callbacks:ready() + self.data.conn:resume(); +end + +function runner_callbacks:waiting() + self.data.conn:pause(); +end + +function runner_callbacks:error(err) + log("error", "Traceback[httpserver]: %s", err); + self.data.conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = err })); + self.data.conn:close(); +end + +local function noop() end function listener.onconnect(conn) + local session = { conn = conn }; local secure = conn:ssl() and true or nil; - local pending = {}; - local waiting = false; - local function process_next() - if waiting then return; end -- log("debug", "can't process_next, waiting"); - waiting = true; - while sessions[conn] and #pending > 0 do - local request = t_remove(pending); - --log("debug", "process_next: %s", request.path); - if not xpcall(handle_request, _traceback_handler, conn, request, process_next) 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 + local ip = conn:ip(); + session.thread = async.runner(function (request) + local wait, done; + if request.partial == true then + -- Have the header for a request, we want to receive the rest + -- when we've decided where the data should go. + wait, done = noop, noop; + else -- Got the entire request + -- Hold off on receiving more incoming requests until this one has been handled. + wait, done = async.waiter(); end - --log("debug", "ready for more"); - waiting = false; - end + handle_request(conn, request, done); wait(); + end, runner_callbacks, session); local function success_cb(request) --log("debug", "success_cb: %s", request.path); - if waiting then - log("error", "http connection handler is not reentrant: %s", request.path); - assert(false, "http connection handler is not reentrant"); - end + request.ip = ip; request.secure = secure; - t_insert(pending, request); - process_next(); + session.thread:run(request); 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 local function options_cb() return options; end - sessions[conn] = parser_new(success_cb, error_cb, "server", options_cb); + session.parser = parser_new(success_cb, error_cb, "server", options_cb); + sessions[conn] = session; end function listener.ondisconnect(conn) @@ -152,7 +161,7 @@ function listener.ondetach(conn) end function listener.onincoming(conn, data) - sessions[conn]:feed(data); + sessions[conn].parser:feed(data); end function listener.ondrain(conn) @@ -170,6 +179,49 @@ local headerfix = setmetatable({}, { end }); +local function handle_result(request, response, result) + if result == nil then + result = 404; + end + + if result == true then + return; + end + + local body; + local result_type = type(result); + if result_type == "number" then + response.status_code = result; + if result >= 400 then + body = events.fire_event("http-error", { request = request, response = response, code = result }); + end + elseif result_type == "string" then + body = result; + elseif errors.is_err(result) then + response.status_code = result.code or 500; + body = events.fire_event("http-error", { request = request, response = response, code = result.code or 500, error = result }); + elseif promise.is_promise(result) then + result:next(function (ret) + handle_result(request, response, ret); + end, function (err) + response.status_code = 500; + handle_result(request, response, err or 500); + end); + return true; + elseif result_type == "table" then + for k, v in pairs(result) do + if k ~= "headers" then + response[k] = v; + else + for header_name, header_value in pairs(v) do + response.headers[header_name] = header_value; + end + end + end + end + return response:send(body); +end + function _M.hijack_response(response, listener) -- luacheck: ignore error("TODO"); end @@ -194,8 +246,11 @@ function handle_request(conn, request, finish_cb) response_conn_header = httpversion == "1.1" and "close" or nil end + local is_head_request = request.method == "HEAD"; + local response = { request = request; + is_head_request = is_head_request; status_code = 200; headers = { date = date_header, connection = response_conn_header }; persistent = persistent; @@ -227,6 +282,11 @@ function handle_request(conn, request, finish_cb) local payload = { request = request, response = response }; log("debug", "Firing event: %s", global_event); local result = events.fire_event(global_event, payload); + if result == nil and is_head_request then + local global_head_event = "GET "..request.path:match("[^?]*"); + log("debug", "Firing event: %s", global_head_event); + result = events.fire_event(global_head_event, payload); + end if result == nil then if not hosts[host] then if hosts[default_host] then @@ -247,40 +307,17 @@ function handle_request(conn, request, finish_cb) local host_event = request.method.." "..host..request.path:match("[^?]*"); log("debug", "Firing event: %s", host_event); result = events.fire_event(host_event, payload); - end - if result ~= nil then - if result ~= true then - local body; - local result_type = type(result); - if result_type == "number" then - response.status_code = result; - if result >= 400 then - payload.code = result; - body = events.fire_event("http-error", payload); - end - elseif result_type == "string" then - body = result; - elseif result_type == "table" then - for k, v in pairs(result) do - if k ~= "headers" then - response[k] = v; - else - for header_name, header_value in pairs(v) do - response.headers[header_name] = header_value; - end - end - end - end - response:send(body); + + if result == nil and is_head_request then + local host_head_event = "GET "..host..request.path:match("[^?]*"); + log("debug", "Firing event: %s", host_head_event); + result = events.fire_event(host_head_event, payload); end - return; end - -- if handler not called, return 404 - response.status_code = 404; - payload.code = 404; - response:send(events.fire_event("http-error", payload)); + return handle_result(request, response, result); end + local function prepare_header(response) local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); local headers = response.headers; @@ -292,12 +329,21 @@ local function prepare_header(response) return output; end _M.prepare_header = prepare_header; +function _M.send_head_response(response) + if response.finished then return; end + local output = prepare_header(response); + response.conn:write(t_concat(output)); + response:done(); +end function _M.send_response(response, body) if response.finished then return; end body = body or response.body or ""; -- Per RFC 7230, informational (1xx) and 204 (no content) should have no c-l header if response.status_code > 199 and response.status_code ~= 204 then - response.headers.content_length = #body; + response.headers.content_length = ("%d"):format(#body); + end + if response.is_head_request then + return _M.send_head_response(response) end local output = prepare_header(response); t_insert(output, body); @@ -305,6 +351,10 @@ function _M.send_response(response, body) response:done(); end function _M.send_file(response, f) + if response.is_head_request then + if f.close then f:close(); end + return _M.send_head_response(response); + end if response.finished then return; end local chunked = not response.headers.content_length; if chunked then response.headers.transfer_encoding = "chunked"; end |