Skip to content

Commit

Permalink
Add an option to control keep-alive connections
Browse files Browse the repository at this point in the history
By default http module set keep-alive option in connections to make it
persistent. However sometimes we need persistent connection and close it
instead. It is possible with option keepalive_disable. Option accepts a
map where key is a user agent name with assigned non-nil value.
Note: nginx web-server has a similar option, see [1].

Unfortunately http client [1] builtin into Tarantool supports only HTTP
protocol version 1.1, so version 1.0 is untested.

Example:

local httpd = http_server.new('127.0.0.1', 8080, {
    log_requests = true,
    log_errors = true,
    keepalive_disable = {
        ['curl/7.68.0'] = true
    },
})

1. https://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_disable
2. https://www.tarantool.io/en/doc/latest/reference/reference_lua/http/

Part of #137
  • Loading branch information
ligurio committed Jan 27, 2022
1 parent a17e710 commit 9475480
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Add option to control keepalive connection state (#137).

## [1.2.0] - 2021-11-10

### Changed
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,22 @@ httpd = require('http.server').new(host, port[, { options } ])

By default uses `log.info` function for requests logging.
* `log_errors` - same as the `log_requests` option but is used for error messages logging. By default uses `log.error()` function.
* `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.

Example:

```lua
local httpd = http_server.new('127.0.0.1', 8080, {
log_requests = true,
log_errors = true,
keepalive_disable = {
['curl/7.68.0'] = true
},
})
```


## Using routes

Expand Down
23 changes: 23 additions & 0 deletions http/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ local function sprintf(fmt, ...)
return string.format(fmt, ...)
end

local function is_no_keepalive(disallowed_map, useragent)
if useragent == nil then
return false
end
if disallowed_map[useragent] == true then
return true
end

return false
end

local function valid_cookie_value_byte(byte)
-- https://tools.ietf.org/html/rfc6265#section-4.1.1
-- US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon,
Expand Down Expand Up @@ -891,6 +902,12 @@ local function process_client(self, s, peer)
end
end

local no_keepalive = is_no_keepalive(self.options.keepalive_disable,
p.headers['user-agent'])
if no_keepalive == true then
hdrs.connection = 'close'
end

local response = {
"HTTP/1.1 ";
status;
Expand Down Expand Up @@ -1282,6 +1299,11 @@ local exports = {
if type(options) ~= 'table' then
errorf("options must be table not '%s'", type(options))
end
options.keepalive_disable = options.keepalive_disable or {}
if type(options.keepalive_disable) ~= 'table' then
error('Option keepalive_disable must be a table.')
end

local default = {
max_header_size = 4096,
header_timeout = 100,
Expand All @@ -1294,6 +1316,7 @@ local exports = {
log_requests = true,
log_errors = true,
display_errors = false,
keepalive_disable = {},
}

local self = {
Expand Down
75 changes: 75 additions & 0 deletions test/integration/http_server_options_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local t = require('luatest')
local http_client = require('http.client')

local helpers = require('test.helpers')

local g = t.group()

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

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

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

g.before_test('test_keepalive_disallowed', function()
local useragent = 'Mozilla/4.0'
g.httpd = helpers.cfgserv({
keepalive_disable = {
[useragent] = true,
}
})
g.httpd:start()
end)

g.after_each(function()
helpers.teardown(g.httpd)
end)

g.test_keepalive_default = function(g)
local httpd = g.httpd
t.assert_equals(httpd.options.keepalive_disable, {})
end

g.test_keepalive_allowed = function()
local useragent = 'Mozilla/4.0'
local r = http_client.request('GET', helpers.base_uri .. '/test', nil, {
headers = {
['user-agent'] = useragent,
connection = 'keep-alive'
},
})
t.assert_equals(r.status, 200)
t.assert_equals(r.headers.connection, 'keep-alive')
end

g.test_keepalive_disallowed = function()
local useragent = 'Mozilla/4.0'
local r = http_client.request('GET', helpers.base_uri .. '/test', nil, {
headers = {
['user-agent'] = useragent,
connection = 'keep-alive'
},
})
t.assert_equals(r.status, 200)
t.assert_equals(r.headers.connection, 'close')
end

g.test_keepalive_change_in_runtime = function(g)
local httpd = g.httpd
t.assert_equals(httpd.options.keepalive_disable, {})
httpd.options.keepalive_disable = {
['Mozilla/4.0'] = true
}
t.assert_equals(httpd.options.keepalive_disable, {
["Mozilla/4.0"] = true
})
end

0 comments on commit 9475480

Please sign in to comment.