diff --git a/CHANGELOG.md b/CHANGELOG.md index f956001..55fa254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.1.1] - 2021-10-28 ### Changed diff --git a/README.md b/README.md index 7d0e8a2..b30988e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,9 @@ 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. ## Using routes diff --git a/http/server.lua b/http/server.lua index 6506402..fa24ac8 100644 --- a/http/server.lua +++ b/http/server.lua @@ -23,6 +23,17 @@ local function sprintf(fmt, ...) return string.format(fmt, ...) end +local function is_no_keepalive(disallowed_map, useragent) + if type(disallowed_map) ~= 'table' or useragent == nil then + return false + end + if disallowed_map[useragent] ~= nil 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, @@ -887,6 +898,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; @@ -1290,6 +1307,7 @@ local exports = { log_requests = true, log_errors = true, display_errors = true, + keepalive_disable = {}, } local self = { diff --git a/test/integration/http_server_options_test.lua b/test/integration/http_server_options_test.lua new file mode 100644 index 0000000..3cb2a59 --- /dev/null +++ b/test/integration/http_server_options_test.lua @@ -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