From 883f2ac73eed7f4590f0fdbec3a128c93b67fb17 Mon Sep 17 00:00:00 2001 From: Paolo Venturi <37150740+pv3nturi@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:31:10 +0200 Subject: [PATCH] =?UTF-8?q?#354=20HttpClient=20>=20Add=20keepAlive=20confi?= =?UTF-8?q?guration=20options=20TCP=5FKEEPIDLE,TC=E2=80=A6=20(#355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #354 HttpClient > Add keepAlive configuration options TCP_KEEPIDLE,TCP_KEEPINTVL,TCP_KEEPCNT * ui + test Co-authored-by: Paolo Venturi - Diennea --- .../api/ConnectionPoolsResource.java | 11 +++++++++- .../core/ProxyRequestsManager.java | 11 +++++++++- .../core/RuntimeServerConfiguration.java | 22 ++++++++++++++++++- .../config/ConnectionPoolConfiguration.java | 4 ++++ .../backends/ConnectionPoolTest.java | 22 ++++++++++++------- .../webapp/src/components/ConnectionPools.vue | 15 +++++++++++++ 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/carapace-server/src/main/java/org/carapaceproxy/api/ConnectionPoolsResource.java b/carapace-server/src/main/java/org/carapaceproxy/api/ConnectionPoolsResource.java index 0fcddbc8..f9b8e188 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/api/ConnectionPoolsResource.java +++ b/carapace-server/src/main/java/org/carapaceproxy/api/ConnectionPoolsResource.java @@ -60,9 +60,12 @@ public static final class ConnectionPoolBean { private int stuckRequestTimeout; private int idleTimeout; private int disposeTimeout; + private int keepaliveIdle; + private int keepaliveInterval; + private int keepaliveCount; private boolean enabled; - private int totalConnections; + private int totalConnections; } @GET @@ -89,6 +92,9 @@ public Map getAll() { conf.getStuckRequestTimeout(), conf.getIdleTimeout(), conf.getDisposeTimeout(), + conf.getKeepaliveIdle(), + conf.getKeepaliveInterval(), + conf.getKeepaliveCount(), conf.isEnabled(), poolsStats.getOrDefault(conf.getId(), 0) ); @@ -107,6 +113,9 @@ public Map getAll() { defaultConnectionPool.getStuckRequestTimeout(), defaultConnectionPool.getIdleTimeout(), defaultConnectionPool.getDisposeTimeout(), + defaultConnectionPool.getKeepaliveIdle(), + defaultConnectionPool.getKeepaliveInterval(), + defaultConnectionPool.getKeepaliveCount(), defaultConnectionPool.isEnabled(), poolsStats.getOrDefault(defaultConnectionPool.getId(), 0) )); diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java index 9d052612..1e9251dd 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java @@ -25,6 +25,8 @@ import com.google.common.annotations.VisibleForTesting; import io.micrometer.core.instrument.Metrics; import io.netty.channel.ChannelOption; +import io.netty.channel.epoll.EpollChannelOption; +import io.netty.channel.socket.nio.NioChannelOption; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpResponse; @@ -51,6 +53,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; +import jdk.net.ExtendedSocketOptions; import org.carapaceproxy.EndpointStats; import org.carapaceproxy.SimpleHTTPResponse; import org.carapaceproxy.server.mapper.MapResult; @@ -324,8 +327,14 @@ public Publisher forward(ProxyRequest request, boolean cache) { .followRedirect(false) // clients has to request the redirect, not the proxy .compress(parent.getCurrentConfiguration().isRequestCompressionEnabled()) .responseTimeout(Duration.ofMillis(connectionConfig.getStuckRequestTimeout())) - .option(ChannelOption.SO_KEEPALIVE, true) // Enables TCP keepalive: TCP starts sending keepalive probes when a connection is idle for some time. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionConfig.getConnectTimeout()) + .option(ChannelOption.SO_KEEPALIVE, true) // Enables TCP keepalive: TCP starts sending keepalive probes when a connection is idle for some time. + .option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), connectionConfig.getKeepaliveIdle()) + .option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), connectionConfig.getKeepaliveInterval()) + .option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), connectionConfig.getKeepaliveCount()) + .option(EpollChannelOption.TCP_KEEPIDLE, connectionConfig.getKeepaliveIdle()) + .option(EpollChannelOption.TCP_KEEPINTVL, connectionConfig.getKeepaliveInterval()) + .option(EpollChannelOption.TCP_KEEPCNT, connectionConfig.getKeepaliveCount()) .doOnRequest((req, conn) -> { if (CarapaceLogger.isLoggingDebugEnabled()) { CarapaceLogger.debug("Start sending request for " diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java index 1bc7d0b5..41aca3fb 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java @@ -70,6 +70,9 @@ public class RuntimeServerConfiguration { private int connectTimeout = 10_000; private int borrowTimeout = 60_000; private int disposeTimeout = 300_000; // 5 min; + private int keepaliveIdle = 300; // sec + private int keepaliveInterval = 60; // sec + private int keepaliveCount = 8; private long cacheMaxSize = 0; private long cacheMaxFileSize = 0; private boolean cacheDisabledForSecureRequestsWithoutPublic = false; @@ -109,6 +112,9 @@ public RuntimeServerConfiguration() { stuckRequestTimeout, idleTimeout, disposeTimeout, + keepaliveIdle, + keepaliveInterval, + keepaliveCount, true ); } @@ -125,13 +131,18 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali this.connectTimeout = properties.getInt("connectionsmanager.connecttimeout", connectTimeout); this.borrowTimeout = properties.getInt("connectionsmanager.borrowtimeout", borrowTimeout); this.disposeTimeout = properties.getInt("connectionsmanager.disposetimeout", disposeTimeout); + this.keepaliveIdle = properties.getInt("connectionsmanager.keepaliveidle", keepaliveIdle); + this.keepaliveInterval = properties.getInt("connectionsmanager.keepaliveinterval", keepaliveInterval); + this.keepaliveCount = properties.getInt("connectionsmanager.keepalivecount", keepaliveCount); LOG.log(Level.INFO, "connectionsmanager.maxconnectionsperendpoint={0}", maxConnectionsPerEndpoint); LOG.log(Level.INFO, "connectionsmanager.idletimeout={0}", idleTimeout); LOG.log(Level.INFO, "connectionsmanager.stuckrequesttimeout={0}", stuckRequestTimeout); LOG.log(Level.INFO, "connectionsmanager.backendsunreachableonstuckrequests={0}", backendsUnreachableOnStuckRequests); LOG.log(Level.INFO, "connectionsmanager.connecttimeout={0}", connectTimeout); LOG.log(Level.INFO, "connectionsmanager.borrowtimeout={0}", borrowTimeout); - LOG.log(Level.INFO, "connectionsmanager.disposetimeout={0}", disposeTimeout); + LOG.log(Level.INFO, "connectionsmanager.keepaliveidle={0}", keepaliveIdle); + LOG.log(Level.INFO, "connectionsmanager.keepaliveinterval={0}", keepaliveInterval); + LOG.log(Level.INFO, "connectionsmanager.keepalivecount={0}", keepaliveCount); this.mapperClassname = properties.getClassname("mapper.class", StandardEndpointMapper.class.getName()); LOG.log(Level.INFO, "mapper.class={0}", this.mapperClassname); @@ -305,6 +316,9 @@ private void configureConnectionPools(ConfigurationStore properties) throws Conf int stuckrequesttimeout = properties.getInt(prefix + "stuckrequesttimeout", stuckRequestTimeout); int idletimeout = properties.getInt(prefix + "idletimeout", idleTimeout); int disposetimeout = properties.getInt(prefix + "disposetimeout", disposeTimeout); + int keepaliveidle = properties.getInt(prefix + "keepaliveidle", keepaliveIdle); + int keepaliveinterval = properties.getInt(prefix + "keepaliveinterval", keepaliveInterval); + int keepalivecount = properties.getInt(prefix + "keepalivecount", keepaliveCount); boolean enabled = properties.getBoolean(prefix + "enabled", false); ConnectionPoolConfiguration connectionPool = new ConnectionPoolConfiguration( @@ -315,6 +329,9 @@ private void configureConnectionPools(ConfigurationStore properties) throws Conf stuckrequesttimeout, idletimeout, disposetimeout, + keepaliveidle, + keepaliveinterval, + keepalivecount, enabled ); connectionPools.add(connectionPool); @@ -330,6 +347,9 @@ private void configureConnectionPools(ConfigurationStore properties) throws Conf getStuckRequestTimeout(), getIdleTimeout(), getDisposeTimeout(), + getKeepaliveIdle(), + getKeepaliveInterval(), + getKeepaliveCount(), true ); LOG.log(Level.INFO, "Configured default connectionpool: {0}", defaultConnectionPool); diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/ConnectionPoolConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/ConnectionPoolConfiguration.java index 99bc8b5b..260f8759 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/ConnectionPoolConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/ConnectionPoolConfiguration.java @@ -37,6 +37,10 @@ public class ConnectionPoolConfiguration { private int stuckRequestTimeout; private int idleTimeout; private int disposeTimeout; + private int keepaliveIdle; + private int keepaliveInterval; + private int keepaliveCount; + private boolean enabled; } diff --git a/carapace-server/src/test/java/org/carapaceproxy/backends/ConnectionPoolTest.java b/carapace-server/src/test/java/org/carapaceproxy/backends/ConnectionPoolTest.java index 21ebb72e..0c6b8eb5 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/backends/ConnectionPoolTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/backends/ConnectionPoolTest.java @@ -122,6 +122,9 @@ private void configureAndStartServer() throws Exception { config.put("connectionsmanager.stuckrequesttimeout", "15000"); config.put("connectionsmanager.idletimeout", "20000"); config.put("connectionsmanager.disposetimeout", "50000"); + config.put("connectionsmanager.keepaliveidle", "500"); + config.put("connectionsmanager.keepaliveinterval", "50"); + config.put("connectionsmanager.keepalivecount", "5"); // Custom connection pool (with defaults) config.put("connectionpool.1.id", "localhost"); @@ -142,6 +145,9 @@ private void configureAndStartServer() throws Exception { config.put("connectionpool.3.stuckrequesttimeout", "23000"); config.put("connectionpool.3.idletimeout", "24000"); config.put("connectionpool.3.disposetimeout", "25000"); + config.put("connectionpool.3.keepaliveidle", "250"); + config.put("connectionpool.3.keepaliveinterval", "25"); + config.put("connectionpool.3.keepalivecount", "2"); config.put("connectionpool.3.enabled", "true"); changeDynamicConfiguration(config); @@ -159,7 +165,7 @@ public void test() throws Exception { // default pool ConnectionPoolConfiguration defaultPool = new ConnectionPoolConfiguration( - "*", "*", 10, 5_000, 10_000, 15_000, 20_000, 50_000, true + "*", "*", 10, 5_000, 10_000, 15_000, 20_000, 50_000, 500, 50, 5, true ); { ConnectionProvider provider = connectionPools.get(defaultPool); @@ -171,7 +177,7 @@ public void test() throws Exception { // pool with defaults ConnectionPoolConfiguration poolWithDefaults = new ConnectionPoolConfiguration( - "localhost", "localhost", 10, 5_000, 10_000, 15_000, 20_000, 50_000, true + "localhost", "localhost", 10, 5_000, 10_000, 15_000, 20_000, 50_000, 500, 50, 5, true ); { ConnectionProvider provider = connectionPools.get(poolWithDefaults); @@ -183,7 +189,7 @@ public void test() throws Exception { // custom pool ConnectionPoolConfiguration customPool = new ConnectionPoolConfiguration( - "localhosts", "localhost[0-9]", 20, 21_000, 22_000, 23_000, 24_000, 25_000, true + "localhosts", "localhost[0-9]", 20, 21_000, 22_000, 23_000, 24_000, 25_000, 250, 25, 2, true ); { ConnectionProvider provider = connectionPools.get(customPool); @@ -196,7 +202,7 @@ public void test() throws Exception { // connection pool selection ProxyRequestsManager.ConnectionsManager connectionsManager = server.getProxyRequestsManager().getConnectionsManager(); - // default providder + // default provider { HttpServerRequest request = mock(HttpServerRequest.class); ProxyRequest proxyRequest = mock(ProxyRequest.class); @@ -288,22 +294,22 @@ public void testAPIResource() throws Exception { // default pool assertThat(pools.get("*"), is(new ConnectionPoolsResource.ConnectionPoolBean( - "*", "*", 10, 5_000, 10_000, 15_000, 20_000, 50_000, true, 0 + "*", "*", 10, 5_000, 10_000, 15_000, 20_000, 50_000, 500, 50, 5, true, 0 ))); // pool with defaults assertThat(pools.get("localhost"), is(new ConnectionPoolsResource.ConnectionPoolBean( - "localhost", "localhost", 10, 5_000, 10_000, 15_000, 20_000, 50_000, true, 1 + "localhost", "localhost", 10, 5_000, 10_000, 15_000, 20_000, 50_000, 500, 50, 5, true, 1 ))); // disabled custom pool assertThat(pools.get("localhost2"), is(new ConnectionPoolsResource.ConnectionPoolBean( - "localhost2", "localhost2", 10, 5_000, 10_000, 15_000, 20_000, 50_000, false, 0 + "localhost2", "localhost2", 10, 5_000, 10_000, 15_000, 20_000, 50_000, 500, 50, 5, false, 0 ))); // custom pool assertThat(pools.get("localhosts"), is(new ConnectionPoolsResource.ConnectionPoolBean( - "localhosts", "localhost[0-9]", 20, 21_000, 22_000, 23_000, 24_000, 25_000, true, 0 + "localhosts", "localhost[0-9]", 20, 21_000, 22_000, 23_000, 24_000, 25_000, 250, 25, 2, true, 0 ))); } } diff --git a/carapace-ui/src/main/webapp/src/components/ConnectionPools.vue b/carapace-ui/src/main/webapp/src/components/ConnectionPools.vue index 7f351ae4..49c92976 100644 --- a/carapace-ui/src/main/webapp/src/components/ConnectionPools.vue +++ b/carapace-ui/src/main/webapp/src/components/ConnectionPools.vue @@ -56,6 +56,21 @@ label: "Dispose Timeout (ms)", sortable: true }, + { + key: "keepaliveIdle", + label: "Keepalive Idle (sec)", + sortable: true + }, + { + key: "keepaliveInterval", + label: "Keepalive Interval (sec)", + sortable: true + }, + { + key: "keepaliveCount", + label: "Keepalive Count", + sortable: true + }, { key: "enabled", label: "Enabled",