Skip to content

Commit

Permalink
Merge pull request 3scale#1106 from eloycoto/THREESCALE-2886
Browse files Browse the repository at this point in the history
Gateway: Add a option to enable keepalive-timeout
  • Loading branch information
davidor authored Sep 4, 2019
2 parents 107d8bc + 3044596 commit fc75276
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Remove dnsmasq process for APIcast [PR #1090](https://github.com/3scale/APIcast/pull/1090), [THREESCALE-1555](https://issues.jboss.org/browse/THREESCALE-1555)
- Allow to use capture function in liquid templates. [PR #1107](https://github.com/3scale/APIcast/pull/1107), [THREESCALE-1911](https://issues.jboss.org/browse/THREESCALE-1911)
- OAuth 2.0 MTLS policy [PR #1101](https://github.com/3scale/APIcast/pull/1101) [Issue #1003](https://github.com/3scale/APIcast/issues/1003)
- Add an option to enable keepalive_timeout on gateway [THREESCALE-2886](https://issues.jboss.org/browse/THREESCALE-2886) [PR #1106](https://github.com/3scale/APIcast/pull/1106)


### Fixed
Expand Down
13 changes: 13 additions & 0 deletions doc/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,16 @@ The metrics that will have extended information are:
- total_response_time_seconds: labels service_id and service_system_name
- upstream_response_time_seconds: labels service_id and service_system_name
- upstream_status: labels service_id and service_system_name

### `HTTP_KEEPALIVE_TIMEOUT`

**Default:** 75
**Value:** positive integers
**Example:** "1"

This parameter sets a timeout during which a keep-alive client connection will
stay open on the server side. The zero value disables keep-alive client
connections.

By default Gateway does not enable it, and the keepalive timeout on nginx is set
to [75 seconds](http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout)
8 changes: 7 additions & 1 deletion gateway/conf.d/apicast.conf
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,14 @@ location / {
set $post_action_impact '';
set $original_request_id '';

proxy_ignore_client_abort on;
# {% if http_keepalive_timeout != empty %}
# {% capture keepalive_timeout %}
#{#} keepalive_timeout {{ http_keepalive_timeout}};
# {% endcapture %}
# {{ keepalive_timeout | replace: "#{#}", "" }}
# {% endif %}

proxy_ignore_client_abort on;
rewrite_by_lua_block {
require('resty.ctx').stash()
require('apicast.executor'):rewrite()
Expand Down
4 changes: 4 additions & 0 deletions gateway/http.d/apicast.conf.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ server {

server_name _;

{% if http_keepalive_timeout != empty %}
keepalive_timeout {{ http_keepalive_timeout}};
{% endif %}

{% if opentracing_tracer != empty %}
opentracing_operation_name "apicast";
opentracing_trace_locations on;
Expand Down
1 change: 1 addition & 0 deletions gateway/src/apicast/cli/environment.lua
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ _M.default_config = {
opentracing_config = env_value_ref('OPENTRACING_CONFIG'),
opentracing_forward_header = env_value_ref('OPENTRACING_FORWARD_HEADER'),
upstream_retry_cases = env_value_ref('APICAST_UPSTREAM_RETRY_CASES'),
http_keepalive_timeout = env_value_ref('HTTP_KEEPALIVE_TIMEOUT'),
policy_chain = require('apicast.policy_chain').default(),
nameservers = parse_nameservers(),
worker_processes = cpus() or 'auto',
Expand Down
237 changes: 237 additions & 0 deletions t/http-keepalive-timeout.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
use lib 't';
use Test::APIcast::Blackbox 'no_plan';

repeat_each(1);

run_tests();

__DATA__

=== TEST 1: Keepalive is not set if no env variable
--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"hosts": [
"one"
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 1 }
],
"policy_chain": [
{ "name": "apicast.policy.apicast" }
]
}
}
]
}
--- upstream env
location / {
content_by_lua_block {
ngx.say('yay, api backend');
}
}
--- backend
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(200)
}
}
--- test env
content_by_lua_block {
local sock = ngx.socket.tcp()
local ok, err = sock:connect(ngx.var.server_addr, ngx.var.apicast_port)
if not ok then
ngx.say("failed to connect: ", err)
ngx.exit(400)
return
end

local function send_request(sock)
local bytes, err = sock:send("GET /?user_key=foo HTTP/1.1\r\nHost:one\r\n\r\n")
if not bytes then
return false, string.format("failed to send bytes: %s", err)
end

local data, err = sock:receiveany(10 * 1024) -- read any data, at most 10K
if not data then
return false, string.format("failed to receive data: %s", err)
end

return true, nil
end

local result, err = send_request(sock)
ngx.say("First request status: ", result, " err:", err)
ngx.sleep(1)
local result, err = send_request(sock)
ngx.say("Second request status: ", result, " err:", err)
}
--- response_body
First request status: true err:nil
Second request status: true err:nil
--- no_error_log
[error]

=== TEST 2: Keepalive timeout cleans correctly on timeout.
--- env eval
("HTTP_KEEPALIVE_TIMEOUT", "0")
--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"hosts": [
"one"
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 1 }
],
"policy_chain": [
{ "name": "apicast.policy.apicast" }
]
}
}
]
}
--- upstream env
location / {
content_by_lua_block {
ngx.say('yay, api backend');
}
}
--- backend
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(200)
}
}
--- test env
content_by_lua_block {
local sock = ngx.socket.tcp()
local ok, err = sock:connect(ngx.var.server_addr, ngx.var.apicast_port)
if not ok then
ngx.say("failed to connect: ", err)
ngx.exit(400)
return
end

local function send_request(sock)
local bytes, err = sock:send("GET /?user_key=foo HTTP/1.1\r\nHost:one\r\n\r\n")
if not bytes then
return false, string.format("failed to send bytes: %s", err)
end

local data, err = sock:receiveany(10 * 1024) -- read any data, at most 10K
if not data then
return false, string.format("failed to receive data: %s", err)
end

return true, nil
end

local result, err = send_request(sock)
ngx.say("First request status: ", result, " err:", err)
ngx.sleep(1)

result, err = send_request(sock)
ngx.say("Second request status: ", result, " err:", err)
}
--- response_body
First request status: true err:nil
Second request status: false err:failed to receive data: closed
--- no_error_log
[error]

=== TEST 3: Keepalive timeout can be set correctly.
--- env eval
(
"HTTP_KEEPALIVE_TIMEOUT" => 2
)
--- timeout: 10s
--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"hosts": [
"one"
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 1 }
],
"policy_chain": [
{ "name": "apicast.policy.apicast" }
]
}
}
]
}
--- upstream env
location / {
content_by_lua_block {
ngx.say('yay, api backend');
}
}
--- backend
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(200)
}
}
--- test env
content_by_lua_block {
local sock = ngx.socket.tcp()
local ok, err = sock:connect(ngx.var.server_addr, ngx.var.apicast_port)
if not ok then
ngx.say("failed to connect: ", err)
ngx.exit(400)
return
end

local function send_request(sock)
local bytes, err = sock:send("GET /?user_key=foo HTTP/1.1\r\nHost:one\r\n\r\n")
if not bytes then
return false, string.format("failed to send bytes: %s", err)
end

local data, err = sock:receiveany(10 * 1024) -- read any data, at most 10K
if not data then
return false, string.format("failed to receive data: %s", err)
end

return true, nil
end

local result, err = send_request(sock)
ngx.say("First request status: ", result, " err:", err)
ngx.sleep(1)
result, err = send_request(sock)
ngx.say("Second request status: ", result, " err:", err)

ngx.sleep(3)
result, err = send_request(sock)
ngx.say("Third request status: ", result, " err:", err)
}
--- response_body
First request status: true err:nil
Second request status: true err:nil
Third request status: false err:failed to receive data: closed
--- no_error_log
[error]

0 comments on commit fc75276

Please sign in to comment.