Skip to content

Commit

Permalink
Add option idle_timeout
Browse files Browse the repository at this point in the history
The main problem with current implementation of HTTP server is it's
cooperative scheduling. To control connections we need invent something
that will periodically checks open connections and close idles with
exceeded timeout. In proposed implementation we close idle connections
with exceeded idle_timeout in synchronous manner - before start
processing a new connection. In comparison to other solutions (like
running checking function in a separate fiber) closing connections
synchronously is much easier to implement and don't make module complex.

Fixes #137
  • Loading branch information
ligurio committed Mar 23, 2022
1 parent 56a7e58 commit dc5af93
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ httpd = require('http.server').new(host, port[, { options } ])
* `keepalive_disable` - disables keep-alive connections with misbehaving
clients. Parameter accept a map that contains a user agents with non-nil
value. By default map is empty.
* `idle_timeout` - maximum amount of time an idle (keep-alive) connection will
remain idle before closing. When the idle timeout is exceeded, HTTP server
closes the keepalive connection. Default value: 0 seconds (disabled).
Example:
Expand Down
39 changes: 39 additions & 0 deletions http/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
local lib = require('http.lib')

local fio = require('fio')
local fiber = require('fiber')
local require = require
local package = package
local mime_types = require('http.mime_types')
Expand All @@ -15,6 +16,17 @@ local errno = require 'errno'

local DETACHED = 101

local function close_idle_connections(connections, idle_timeout, logger)
-- FIXME: complexity is O(N) and it is bad
for fiber_obj, start_time in pairs(connections) do
if fiber.clock() - start_time >= idle_timeout then
logger("Connection closed, idle_timeout was exceeded (fiber id %d)\n", fiber_obj:id())
fiber_obj:cancel()
connections[fiber_obj] = nil
end
end
end

local function errorf(fmt, ...)
error(string.format(fmt, ...))
end
Expand Down Expand Up @@ -756,6 +768,16 @@ end
local function process_client(self, s, peer)
while true do
local hdrs = ''
-- Create a simple logger without route_opts as it is required for
-- logging closed idle connections. Below we will create another one logger
-- with route information.
local logger = get_request_logger(self.options)

if self.options.idle_timeout > 0 then
local id = fiber.id()
self.connections[id] = fiber.clock()
close_idle_connections(self.connections, self.options.idle_timeout, logger)
end

local is_eof = false
while true do
Expand Down Expand Up @@ -979,6 +1001,9 @@ local function httpd_stop(self)
self.tcp_server:close()
self.tcp_server = nil
end

self.connections = {}

return self
end

Expand Down Expand Up @@ -1306,6 +1331,14 @@ local exports = {
if type(options.keepalive_disable) ~= 'table' then
error('Option keepalive_disable must be a table.')
end
if options.idle_timeout ~= nil and
type(options.idle_timeout) ~= 'number' then
error('Option idle_timeout must be a number.')
end
if options.idle_connections ~= nil and
type(options.idle_connections) ~= 'number' then
error('Option idle_connections must be a number.')
end

local default = {
max_header_size = 4096,
Expand All @@ -1320,6 +1353,8 @@ local exports = {
log_errors = true,
display_errors = false,
keepalive_disable = {},
idle_timeout = 0, -- no timeout, option is disabled
idle_connections = 0, -- infinite, option is disabled
}

local self = {
Expand Down Expand Up @@ -1353,6 +1388,10 @@ local exports = {
ctx = {},
static = {},
},

-- A table that maps fiber object to time when it was active last
-- time. Using for tracking idle connections.
connections = {},
}

return self
Expand Down
20 changes: 20 additions & 0 deletions test/integration/http_server_options_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ g.before_test('test_keepalive_disallowed', function()
g.httpd:start()
end)

g.before_test('test_idle_timeout_default', function()
g.httpd = helpers.cfgserv()
g.httpd:start()
end)

g.before_test('test_idle_connections_default', function()
g.httpd = helpers.cfgserv()
g.httpd:start()
end)

g.after_each(function()
helpers.teardown(g.httpd)
end)
Expand Down Expand Up @@ -73,3 +83,13 @@ g.test_keepalive_change_in_runtime = function(g)
["Mozilla/4.0"] = true
})
end

g.test_idle_timeout_default = function(g)
local httpd = g.httpd
t.assert_equals(httpd.options.idle_timeout, 0)
end

g.test_idle_connections_default = function(g)
local httpd = g.httpd
t.assert_equals(httpd.options.idle_connections, 0)
end

0 comments on commit dc5af93

Please sign in to comment.