You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2024/08/10 09:39:58 UTC
(httpcomponents-core) branch master updated: HTTPCORE-761: support ExtendedSocketOption (#473)
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
The following commit(s) were added to refs/heads/master by this push:
new d5f903a7f HTTPCORE-761: support ExtendedSocketOption (#473)
d5f903a7f is described below
commit d5f903a7f7203b50f1d5cfc97eef4279266b2d9c
Author: kkewwei <ke...@bytedance.com>
AuthorDate: Sat Aug 10 17:39:52 2024 +0800
HTTPCORE-761: support ExtendedSocketOption (#473)
---
.../core5/http/impl/bootstrap/HttpRequester.java | 14 +++
.../hc/core5/http/impl/bootstrap/HttpServer.java | 14 +++
.../core5/http/impl/bootstrap/RequestListener.java | 14 +++
.../org/apache/hc/core5/http/io/SocketConfig.java | 86 ++++++++++++++++
.../apache/hc/core5/reactor/IOReactorConfig.java | 77 ++++++++++++++
.../hc/core5/reactor/SingleCoreIOReactor.java | 34 +++++-
.../java/org/apache/hc/core5/util/NetUtils.java | 39 +++++++
.../org/apache/hc/core5/util/ReflectionUtils.java | 57 ++++++++++-
.../hc/core5/reactor/IOReactorConfigTest.java | 6 ++
.../apache/hc/core5/util/TestReflectionUtils.java | 114 +++++++++++++++++++++
10 files changed, 449 insertions(+), 6 deletions(-)
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java
index 608368979..1dacdd236 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java
@@ -82,9 +82,14 @@ import org.apache.hc.core5.pool.ManagedConnPool;
import org.apache.hc.core5.pool.PoolEntry;
import org.apache.hc.core5.pool.PoolStats;
import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.ReflectionUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPCOUNT;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPIDLE;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPINTERVAL;
+
/**
* HTTP/1.1 client side message exchange initiator.
*
@@ -249,6 +254,15 @@ public class HttpRequester implements ConnPoolControl<HttpHost>, ModalCloseable
if (socketConfig.getSndBufSize() > 0) {
sock.setSendBufferSize(socketConfig.getSndBufSize());
}
+ if (this.socketConfig.getTcpKeepIdle() > 0) {
+ ReflectionUtils.setOption(sock, TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
+ }
+ if (this.socketConfig.getTcpKeepInterval() > 0) {
+ ReflectionUtils.setOption(sock, TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
+ }
+ if (this.socketConfig.getTcpKeepCount() > 0) {
+ ReflectionUtils.setOption(sock, TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
+ }
final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
if (linger >= 0) {
sock.setSoLinger(true, linger);
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
index 8c17d1b62..8363c70c2 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
@@ -57,9 +57,14 @@ import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.io.Closer;
import org.apache.hc.core5.io.ModalCloseable;
import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.ReflectionUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPCOUNT;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPIDLE;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPINTERVAL;
+
/**
* HTTP/1.1 server side message exchange handler.
*
@@ -145,6 +150,15 @@ public class HttpServer implements ModalCloseable {
if (this.socketConfig.getRcvBufSize() > 0) {
this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize());
}
+ if (this.socketConfig.getTcpKeepIdle() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
+ }
+ if (this.socketConfig.getTcpKeepInterval() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
+ }
+ if (this.socketConfig.getTcpKeepCount() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
+ }
if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) {
final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket;
final SSLParameters sslParameters = sslServerSocket.getSSLParameters();
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java
index f9b325eaa..2b50caa11 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java
@@ -45,6 +45,11 @@ import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.HttpServerConnection;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.io.Closer;
+import org.apache.hc.core5.util.ReflectionUtils;
+
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPCOUNT;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPIDLE;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPINTERVAL;
class RequestListener implements Runnable {
@@ -91,6 +96,15 @@ class RequestListener implements Runnable {
if (this.socketConfig.getSoLinger().toSeconds() >= 0) {
socket.setSoLinger(true, this.socketConfig.getSoLinger().toSecondsIntBound());
}
+ if (this.socketConfig.getTcpKeepIdle() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
+ }
+ if (this.socketConfig.getTcpKeepInterval() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
+ }
+ if (this.socketConfig.getTcpKeepCount() > 0) {
+ ReflectionUtils.setOption(this.serverSocket, TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
+ }
if (!(socket instanceof SSLSocket) && sslSocketFactory != null) {
final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, null, -1, false);
sslSocket.setUseClientMode(false);
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java
index 492bc0685..ee3824c29 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java
@@ -56,6 +56,9 @@ public class SocketConfig {
private final int sndBufSize;
private final int rcvBufSize;
private final int backlogSize;
+ private final int tcpKeepIdle;
+ private final int tcpKeepInterval;
+ private final int tcpKeepCount;
private final SocketAddress socksProxyAddress;
SocketConfig(
@@ -67,6 +70,9 @@ public class SocketConfig {
final int sndBufSize,
final int rcvBufSize,
final int backlogSize,
+ final int tcpKeepIdle,
+ final int tcpKeepInterval,
+ final int tcpKeepCount,
final SocketAddress socksProxyAddress) {
super();
this.soTimeout = soTimeout;
@@ -77,6 +83,9 @@ public class SocketConfig {
this.sndBufSize = sndBufSize;
this.rcvBufSize = rcvBufSize;
this.backlogSize = backlogSize;
+ this.tcpKeepIdle = tcpKeepIdle;
+ this.tcpKeepInterval = tcpKeepInterval;
+ this.tcpKeepCount = tcpKeepCount;
this.socksProxyAddress = socksProxyAddress;
}
@@ -139,6 +148,30 @@ public class SocketConfig {
return backlogSize;
}
+ /**
+ * @see Builder#setTcpKeepIdle(int)
+ * @since 5.3
+ */
+ public int getTcpKeepIdle() {
+ return this.tcpKeepIdle;
+ }
+
+ /**
+ * @see Builder#setTcpKeepInterval(int)
+ * @since 5.3
+ */
+ public int getTcpKeepInterval() {
+ return this.tcpKeepInterval;
+ }
+
+ /**
+ * @see Builder#setTcpKeepCount(int)
+ * @since 5.3
+ */
+ public int getTcpKeepCount() {
+ return this.tcpKeepCount;
+ }
+
/**
* @see Builder#setSocksProxyAddress(SocketAddress)
*/
@@ -157,6 +190,9 @@ public class SocketConfig {
.append(", sndBufSize=").append(this.sndBufSize)
.append(", rcvBufSize=").append(this.rcvBufSize)
.append(", backlogSize=").append(this.backlogSize)
+ .append(", tcpKeepIdle=").append(this.tcpKeepIdle)
+ .append(", tcpKeepInterval=").append(this.tcpKeepInterval)
+ .append(", tcpKeepCount=").append(this.tcpKeepCount)
.append(", socksProxyAddress=").append(this.socksProxyAddress)
.append("]");
return builder.toString();
@@ -177,6 +213,9 @@ public class SocketConfig {
.setSndBufSize(config.getSndBufSize())
.setRcvBufSize(config.getRcvBufSize())
.setBacklogSize(config.getBacklogSize())
+ .setTcpKeepIdle(config.getTcpKeepIdle())
+ .setTcpKeepInterval(config.getTcpKeepInterval())
+ .setTcpKeepCount(config.getTcpKeepCount())
.setSocksProxyAddress(config.getSocksProxyAddress());
}
@@ -190,6 +229,9 @@ public class SocketConfig {
private int sndBufSize;
private int rcvBufSize;
private int backlogSize;
+ private int tcpKeepIdle;
+ private int tcpKeepInterval;
+ private int tcpKeepCount;
private SocketAddress socksProxyAddress;
Builder() {
@@ -201,6 +243,9 @@ public class SocketConfig {
this.sndBufSize = 0;
this.rcvBufSize = 0;
this.backlogSize = 0;
+ this.tcpKeepIdle = -1;
+ this.tcpKeepInterval = -1;
+ this.tcpKeepCount = -1;
this.socksProxyAddress = null;
}
@@ -340,6 +385,46 @@ public class SocketConfig {
return this;
}
+ /**
+ * Determines the time (in seconds) the connection needs to remain idle before TCP starts
+ * sending keepalive probes.
+ * <p>
+ * Default: {@code -1} (system default)
+ * </p>
+ * @return the time (in seconds) the connection needs to remain idle before TCP starts
+ * @since 5.3
+ */
+ public Builder setTcpKeepIdle(final int tcpKeepIdle) {
+ this.tcpKeepIdle = tcpKeepIdle;
+ return this;
+ }
+
+ /**
+ * Determines the time (in seconds) between individual keepalive probes.
+ * <p>
+ * Default: {@code -1} (system default)
+ * </p>
+ * @return the time (in seconds) between individual keepalive probes.
+ * @since 5.3
+ */
+ public Builder setTcpKeepInterval(final int tcpKeepInterval) {
+ this.tcpKeepInterval = tcpKeepInterval;
+ return this;
+ }
+
+ /**
+ * Determines the maximum number of keepalive probes TCP should send before dropping the connection.
+ * <p>
+ * Default: {@code -1} (system default)
+ * </p>
+ * @return the maximum number of keepalive probes TCP should send before dropping the connection.
+ * @since 5.3
+ */
+ public Builder setTcpKeepCount(final int tcpKeepCount) {
+ this.tcpKeepCount = tcpKeepCount;
+ return this;
+ }
+
/**
* The address of the SOCKS proxy to use.
*/
@@ -354,6 +439,7 @@ public class SocketConfig {
soReuseAddress,
soLinger != null ? soLinger : TimeValue.NEG_ONE_SECOND,
soKeepAlive, tcpNoDelay, sndBufSize, rcvBufSize, backlogSize,
+ tcpKeepIdle, tcpKeepInterval, tcpKeepCount,
socksProxyAddress);
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java
index 70ef1d7d0..6232ebcdf 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java
@@ -57,6 +57,9 @@ public final class IOReactorConfig {
private final int sndBufSize;
private final int rcvBufSize;
private final int backlogSize;
+ private final int tcpKeepIdle;
+ private final int tcpKeepInterval;
+ private final int tcpKeepCount;
private final SocketAddress socksProxyAddress;
private final String socksProxyUsername;
private final String socksProxyPassword;
@@ -73,6 +76,9 @@ public final class IOReactorConfig {
final int sndBufSize,
final int rcvBufSize,
final int backlogSize,
+ final int tcpKeepIdle,
+ final int tcpKeepInterval,
+ final int tcpKeepCount,
final SocketAddress socksProxyAddress,
final String socksProxyUsername,
final String socksProxyPassword) {
@@ -88,6 +94,9 @@ public final class IOReactorConfig {
this.sndBufSize = sndBufSize;
this.rcvBufSize = rcvBufSize;
this.backlogSize = backlogSize;
+ this.tcpKeepIdle = tcpKeepIdle;
+ this.tcpKeepInterval = tcpKeepInterval;
+ this.tcpKeepCount = tcpKeepCount;
this.socksProxyAddress = socksProxyAddress;
this.socksProxyUsername = socksProxyUsername;
this.socksProxyPassword = socksProxyPassword;
@@ -182,6 +191,33 @@ public final class IOReactorConfig {
return backlogSize;
}
+ /**
+ * @see Builder#setTcpKeepIdle(int)
+ *
+ * @since 5.3
+ */
+ public int getTcpKeepIdle() {
+ return this.tcpKeepIdle;
+ }
+
+ /**
+ * @see Builder#setTcpKeepInterval(int)
+ *
+ * @since 5.3
+ */
+ public int getTcpKeepInterval() {
+ return this.tcpKeepInterval;
+ }
+
+ /**
+ * @see Builder#setTcpKeepCount(int)
+ *
+ * @since 5.3
+ */
+ public int getTcpKeepCount() {
+ return this.tcpKeepCount;
+ }
+
/**
* @see Builder#setSocksProxyAddress(SocketAddress)
*/
@@ -220,6 +256,9 @@ public final class IOReactorConfig {
.setSndBufSize(config.getSndBufSize())
.setRcvBufSize(config.getRcvBufSize())
.setBacklogSize(config.getBacklogSize())
+ .setTcpKeepIdle(config.getTcpKeepIdle())
+ .setTcpKeepInterval(config.getTcpKeepInterval())
+ .setTcpKeepCount(config.getTcpKeepCount())
.setSocksProxyAddress(config.getSocksProxyAddress())
.setSocksProxyUsername(config.getSocksProxyUsername())
.setSocksProxyPassword(config.getSocksProxyPassword());
@@ -265,6 +304,9 @@ public final class IOReactorConfig {
private int sndBufSize;
private int rcvBufSize;
private int backlogSize;
+ private int tcpKeepIdle;
+ private int tcpKeepInterval;
+ private int tcpKeepCount;
private SocketAddress socksProxyAddress;
private String socksProxyUsername;
private String socksProxyPassword;
@@ -281,6 +323,9 @@ public final class IOReactorConfig {
this.sndBufSize = 0;
this.rcvBufSize = 0;
this.backlogSize = 0;
+ this.tcpKeepIdle = -1;
+ this.tcpKeepInterval = -1;
+ this.tcpKeepCount = -1;
this.socksProxyAddress = null;
this.socksProxyUsername = null;
this.socksProxyPassword = null;
@@ -462,6 +507,37 @@ public final class IOReactorConfig {
return this;
}
+ /**
+ * Determines the time (in seconds) the connection needs to remain idle before TCP starts
+ * sending keepalive probes.
+ *
+ * @since 5.3
+ */
+ public Builder setTcpKeepIdle(final int tcpKeepIdle) {
+ this.tcpKeepIdle = tcpKeepIdle;
+ return this;
+ }
+
+ /**
+ * Determines the time (in seconds) between individual keepalive probes.
+ *
+ * @since 5.3
+ */
+ public Builder setTcpKeepInterval(final int tcpKeepInterval) {
+ this.tcpKeepInterval = tcpKeepInterval;
+ return this;
+ }
+
+ /**
+ * Determines the maximum number of keepalive probes TCP should send before dropping the connection.
+ *
+ * @since 5.3
+ */
+ public Builder setTcpKeepCount(final int tcpKeepCount) {
+ this.tcpKeepCount = tcpKeepCount;
+ return this;
+ }
+
/**
* The address of the SOCKS proxy to use.
*/
@@ -497,6 +573,7 @@ public final class IOReactorConfig {
tcpNoDelay,
trafficClass,
sndBufSize, rcvBufSize, backlogSize,
+ tcpKeepIdle, tcpKeepInterval, tcpKeepCount,
socksProxyAddress, socksProxyUsername, socksProxyPassword);
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
index 95d764320..598f27078 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
@@ -31,6 +31,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
+import java.net.SocketOption;
import java.net.UnknownHostException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
@@ -51,6 +52,11 @@ import org.apache.hc.core5.net.NamedEndpoint;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Timeout;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPCOUNT;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPIDLE;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPINTERVAL;
+import static org.apache.hc.core5.util.ReflectionUtils.getExtendedSocketOptionOrNull;
+
class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements ConnectionInitiator {
private static final int MAX_CHANNEL_REQUESTS = 10000;
@@ -186,7 +192,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
final SocketChannel socketChannel = entry.channel;
final Object attachment = entry.attachment;
try {
- prepareSocket(socketChannel.socket());
+ prepareSocket(socketChannel);
socketChannel.configureBlocking(false);
} catch (final IOException ex) {
logException(ex);
@@ -261,7 +267,8 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
return sessionRequest;
}
- private void prepareSocket(final Socket socket) throws IOException {
+ private void prepareSocket(final SocketChannel socketChannel) throws IOException {
+ final Socket socket = socketChannel.socket();
socket.setTcpNoDelay(this.reactorConfig.isTcpNoDelay());
socket.setKeepAlive(this.reactorConfig.isSoKeepAlive());
if (this.reactorConfig.getSndBufSize() > 0) {
@@ -277,6 +284,27 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
if (linger >= 0) {
socket.setSoLinger(true, linger);
}
+ if (this.reactorConfig.getTcpKeepIdle() > 0) {
+ setExtendedSocketOption(socketChannel, TCP_KEEPIDLE, this.reactorConfig.getTcpKeepIdle());
+ }
+ if (this.reactorConfig.getTcpKeepInterval() > 0) {
+ setExtendedSocketOption(socketChannel, TCP_KEEPINTERVAL, this.reactorConfig.getTcpKeepInterval());
+ }
+ if (this.reactorConfig.getTcpKeepInterval() > 0) {
+ setExtendedSocketOption(socketChannel, TCP_KEEPCOUNT, this.reactorConfig.getTcpKeepCount());
+ }
+ }
+
+ /**
+ * @since 5.3
+ */
+ <T> void setExtendedSocketOption(final SocketChannel socketChannel,
+ final String optionName, final T value) throws IOException {
+ final SocketOption<T> socketOption = getExtendedSocketOptionOrNull(optionName);
+ if (socketOption == null) {
+ throw new UnsupportedOperationException(optionName + " is not supported in the current jdk");
+ }
+ socketChannel.setOption(socketOption, value);
}
private void validateAddress(final SocketAddress address) throws UnknownHostException {
@@ -311,7 +339,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
private void processConnectionRequest(final SocketChannel socketChannel, final IOSessionRequest sessionRequest) throws IOException {
socketChannel.configureBlocking(false);
- prepareSocket(socketChannel.socket());
+ prepareSocket(socketChannel);
validateAddress(sessionRequest.localAddress);
if (sessionRequest.localAddress != null) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/NetUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/NetUtils.java
new file mode 100644
index 000000000..5dd3d6aca
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/util/NetUtils.java
@@ -0,0 +1,39 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.util;
+
+import org.apache.hc.core5.annotation.Internal;
+
+@Internal
+public class NetUtils {
+
+ public static final String TCP_KEEPIDLE = "TCP_KEEPIDLE";
+ public static final String TCP_KEEPINTERVAL = "TCP_KEEPINTERVAL";
+ public static final String TCP_KEEPCOUNT = "TCP_KEEPCOUNT";
+
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java
index fad06bca2..08f741b8c 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java
@@ -27,7 +27,10 @@
package org.apache.hc.core5.util;
+import java.io.IOException;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.net.SocketOption;
import org.apache.hc.core5.annotation.Internal;
@@ -45,11 +48,24 @@ public final class ReflectionUtils {
}
public static <T> T callGetter(final Object object, final String getterName, final Class<T> resultType) {
+ return callGetter(object, getterName, null, null, resultType);
+ }
+
+ public static <T> T callGetter(final Object object, final String getterName, final Object arg, final Class argType, final Class<T> resultType) {
try {
final Class<?> clazz = object.getClass();
- final Method method = clazz.getMethod("get" + getterName);
- method.setAccessible(true);
- return resultType.cast(method.invoke(object));
+ final Method method;
+ if (arg != null) {
+ assert argType != null;
+ method = clazz.getMethod("get" + getterName, argType);
+ method.setAccessible(true);
+ return resultType.cast(method.invoke(object, arg));
+ } else {
+ assert argType == null;
+ method = clazz.getMethod("get" + getterName);
+ method.setAccessible(true);
+ return resultType.cast(method.invoke(object));
+ }
} catch (final Exception ignore) {
return null;
}
@@ -72,4 +88,39 @@ public final class ReflectionUtils {
return 7;
}
+ /**
+ * @since 5.3
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> SocketOption<T> getExtendedSocketOptionOrNull(final String fieldName) {
+ try {
+ final Class<?> extendedSocketOptionsClass = Class.forName("jdk.net.ExtendedSocketOptions");
+ final Field field = extendedSocketOptionsClass.getField(fieldName);
+ return (SocketOption)field.get((Object)null);
+ } catch (final Exception ignore) {
+ return null;
+ }
+ }
+
+ /**
+ * object can be ServerSocket or Socket
+ *
+ * @since 5.3
+ */
+ public static <T> void setOption(final T object, final String fieldName, final T value) throws IOException {
+ try {
+ final Class<?> serverSocketClass = object.getClass();
+ final Method setOptionMethod = serverSocketClass.getMethod("setOption", SocketOption.class, Object.class);
+ final SocketOption<Integer> socketOption = getExtendedSocketOptionOrNull(fieldName);
+ if (socketOption == null) {
+ throw new UnsupportedOperationException(fieldName + " is not supported in the current jdk");
+ }
+ setOptionMethod.invoke(object, socketOption, value);
+ } catch (final UnsupportedOperationException e) {
+ throw e;
+ } catch (final Exception e) {
+ throw new IOException("failed to call setOption", e);
+ }
+ }
+
}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java
index 9c4942771..ff1bb120b 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java
@@ -48,6 +48,9 @@ class IOReactorConfigTest {
.setSndBufSize(32767)
.setRcvBufSize(8192)
.setBacklogSize(5)
+ .setTcpKeepIdle(100)
+ .setTcpKeepInterval(12)
+ .setTcpKeepCount(4)
.setSocksProxyAddress(new InetSocketAddress(8888))
.setSocksProxyUsername("socksProxyUsername")
.setSocksProxyPassword("socksProxyPassword")
@@ -64,6 +67,9 @@ class IOReactorConfigTest {
Assertions.assertEquals(32767, reactorConfig.getSndBufSize());
Assertions.assertEquals(8192, reactorConfig.getRcvBufSize());
Assertions.assertEquals(5, reactorConfig.getBacklogSize());
+ Assertions.assertEquals(100, reactorConfig.getTcpKeepIdle());
+ Assertions.assertEquals(12, reactorConfig.getTcpKeepInterval());
+ Assertions.assertEquals(4, reactorConfig.getTcpKeepCount());
Assertions.assertEquals(new InetSocketAddress(8888), reactorConfig.getSocksProxyAddress());
Assertions.assertEquals("socksProxyUsername", reactorConfig.getSocksProxyUsername());
Assertions.assertEquals("socksProxyPassword", reactorConfig.getSocksProxyPassword());
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestReflectionUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestReflectionUtils.java
new file mode 100644
index 000000000..b29c448a9
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestReflectionUtils.java
@@ -0,0 +1,114 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.util;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ServerSocketFactory;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketOption;
+
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPCOUNT;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPIDLE;
+import static org.apache.hc.core5.util.NetUtils.TCP_KEEPINTERVAL;
+import static org.apache.hc.core5.util.ReflectionUtils.callGetter;
+import static org.apache.hc.core5.util.ReflectionUtils.determineJRELevel;
+import static org.apache.hc.core5.util.ReflectionUtils.setOption;
+import static org.apache.hc.core5.util.ReflectionUtils.getExtendedSocketOptionOrNull;
+
+public class TestReflectionUtils {
+
+ @Test
+ public void testGetExtendedSocketOptionOrNull() {
+ testGetExtendedSocketOption(TCP_KEEPIDLE);
+ testGetExtendedSocketOption(TCP_KEEPINTERVAL);
+ testGetExtendedSocketOption(TCP_KEEPCOUNT);
+ }
+
+ private <T> void testGetExtendedSocketOption(final String option) {
+ final SocketOption<T> socketOption = getExtendedSocketOptionOrNull(option);
+ // 1.Partial versions of jdk1.8 contain TCP_KEEPIDLE, TCP_KEEPINTERVAL, TCP_KEEPCOUNT.
+ // 2. Windows may not support TCP_KEEPIDLE, TCP_KEEPINTERVAL, TCP_KEEPCOUNT.
+ if (determineJRELevel() > 8 && isWindows() == false) {
+ Assertions.assertNotNull(socketOption);
+ }
+ }
+
+ @Test
+ public void testSetOption() throws IOException {
+ if (determineJRELevel() > 8 && isWindows() == false) {
+ {
+ // test Socket
+ final Socket sock = new Socket();
+ setOption(sock, TCP_KEEPIDLE, 20);
+ setOption(sock, TCP_KEEPINTERVAL, 21);
+ setOption(sock, TCP_KEEPCOUNT, 22);
+
+ final SocketOption<Integer> tcpKeepIdle = getExtendedSocketOptionOrNull(TCP_KEEPIDLE);
+ assert tcpKeepIdle != null;
+ Assertions.assertEquals(20, callGetter(sock, "Option", tcpKeepIdle, SocketOption.class, Integer.class));
+
+ final SocketOption<Integer> tcpKeepInterval = getExtendedSocketOptionOrNull(TCP_KEEPINTERVAL);
+ assert tcpKeepInterval != null;
+ Assertions.assertEquals(21, callGetter(sock, "Option", tcpKeepInterval, SocketOption.class, Integer.class));
+
+ final SocketOption<Integer> tcpKeepCount = getExtendedSocketOptionOrNull(TCP_KEEPCOUNT);
+ assert tcpKeepCount != null;
+ Assertions.assertEquals(22, callGetter(sock, "Option", tcpKeepCount, SocketOption.class, Integer.class));
+ }
+
+ {
+ // test ServerSocket
+ final ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket();
+ setOption(serverSocket, TCP_KEEPIDLE, 20);
+ setOption(serverSocket, TCP_KEEPINTERVAL, 21);
+ setOption(serverSocket, TCP_KEEPCOUNT, 22);
+
+ final SocketOption<Integer> tcpKeepIdle = getExtendedSocketOptionOrNull(TCP_KEEPIDLE);
+ assert tcpKeepIdle != null;
+ Assertions.assertEquals(20, callGetter(serverSocket, "Option", tcpKeepIdle, SocketOption.class, Integer.class));
+
+ final SocketOption<Integer> tcpKeepInterval = getExtendedSocketOptionOrNull(TCP_KEEPINTERVAL);
+ assert tcpKeepInterval != null;
+ Assertions.assertEquals(21, callGetter(serverSocket, "Option", tcpKeepInterval, SocketOption.class, Integer.class));
+
+ final SocketOption<Integer> tcpKeepCount = getExtendedSocketOptionOrNull(TCP_KEEPCOUNT);
+ assert tcpKeepCount != null;
+ Assertions.assertEquals(22, callGetter(serverSocket, "Option", tcpKeepCount, SocketOption.class, Integer.class));
+ }
+ }
+ }
+
+ public static boolean isWindows() {
+ return System.getProperty("os.name").contains("Windows");
+ }
+
+}