From 711332a63789ec83972287031fc8c53b2a351d19 Mon Sep 17 00:00:00 2001 From: "kewei.11" Date: Sat, 3 Aug 2024 14:49:03 +0800 Subject: [PATCH] support ExtendedSocketOption in HttpRequester, HttpServer, RequestListener --- .../http/impl/bootstrap/HttpRequester.java | 14 +++++ .../core5/http/impl/bootstrap/HttpServer.java | 14 +++++ .../http/impl/bootstrap/RequestListener.java | 14 +++++ .../apache/hc/core5/http/io/SocketConfig.java | 9 ++++ .../apache/hc/core5/util/ReflectionUtils.java | 26 ++++++++-- .../hc/core5/util/TestReflectionUtils.java | 51 +++++++++++++++++++ 6 files changed, 125 insertions(+), 3 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..288ebb4e5 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.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.reactor.SingleCoreIOReactor.TCP_KEEPCOUNT; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPIDLE; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPINTERVAL; + /** * HTTP/1.1 client side message exchange initiator. * @@ -249,6 +254,15 @@ private HttpClientConnection createConnection(final Socket sock, final HttpHost 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..0c1ba832b 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.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.reactor.SingleCoreIOReactor.TCP_KEEPCOUNT; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPIDLE; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPINTERVAL; + /** * HTTP/1.1 server side message exchange handler. * @@ -145,6 +150,15 @@ public void start() throws IOException { 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..ba0f222af 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.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.reactor.SingleCoreIOReactor.TCP_KEEPCOUNT; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPIDLE; +import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPINTERVAL; class RequestListener implements Runnable { @@ -91,6 +96,15 @@ private HttpServerConnection createConnection(final Socket socket) throws IOExce 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 47e3dbba6..bfbb0ba57 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 @@ -150,6 +150,7 @@ public int getBacklogSize() { /** * @see Builder#setTcpKeepIdle(int) + * @since 5.3 */ public int getTcpKeepInterval() { return this.tcpKeepInterval; @@ -157,6 +158,7 @@ public int getTcpKeepInterval() { /** * @see Builder#setTcpKeepInterval(int) + * @since 5.3 */ public int getTcpKeepIdle() { return this.tcpKeepIdle; @@ -164,6 +166,7 @@ public int getTcpKeepIdle() { /** * @see Builder#setTcpKeepCount(int) + * @since 5.3 */ public int getTcpKeepCount() { return this.tcpKeepCount; @@ -388,6 +391,8 @@ public Builder setBacklogSize(final int backlogSize) { *

* Default: {@code -1} (system default) *

+ * @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; @@ -399,6 +404,8 @@ public Builder setTcpKeepIdle(final int tcpKeepIdle) { *

* Default: {@code -1} (system default) *

+ * @return the time (in seconds) between individual keepalive probes. + * @since 5.3 */ public Builder setTcpKeepInterval(final int tcpKeepInterval) { this.tcpKeepInterval = tcpKeepInterval; @@ -410,6 +417,8 @@ public Builder setTcpKeepInterval(final int tcpKeepInterval) { *

* Default: {@code -1} (system default) *

+ * @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; 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 91af71554..232a42dd5 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 @@ -46,12 +46,13 @@ public static void callSetter(final Object object, final String setterName, fina } } - public static T callGetter(final Object object, final String getterName, final Class resultType) { + public static T callGetter(final Object object, final String getterName, final Object arg, final Class argType, final Class resultType) { try { final Class clazz = object.getClass(); - final Method method = clazz.getMethod("get" + getterName); + final Method method; + method = clazz.getMethod("get" + getterName, argType); method.setAccessible(true); - return resultType.cast(method.invoke(object)); + return resultType.cast(method.invoke(object, arg)); } catch (final Exception ignore) { return null; } @@ -86,4 +87,23 @@ public static SocketOption getExtendedSocketOptionOrNull(final String fie return null; } } + + /** + * object can be ServerSocket or Socket + * + * @since 5.3 + */ + public static void setOption(final T object, final String fieldName, final T value) { + try { + final Class serverSocketClass = object.getClass(); + final Method setOptionMethod = serverSocketClass.getMethod("setOption", SocketOption.class, Object.class); + final SocketOption tcpKeepIdle = getExtendedSocketOptionOrNull(fieldName); + if (tcpKeepIdle == null) { + throw new UnsupportedOperationException(fieldName + " is not supported in the current jdk"); + } + setOptionMethod.invoke(object, tcpKeepIdle, value); + } catch (final Exception ignore) { + throw new UnsupportedOperationException(fieldName + " is not supported in the current jdk"); + } + } } 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 index 1b48ee5ca..c735a2b36 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestReflectionUtils.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestReflectionUtils.java @@ -30,12 +30,18 @@ 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.reactor.SingleCoreIOReactor.TCP_KEEPCOUNT; import static org.apache.hc.core5.reactor.SingleCoreIOReactor.TCP_KEEPIDLE; import static org.apache.hc.core5.reactor.SingleCoreIOReactor.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 { @@ -56,6 +62,51 @@ private void testGetExtendedSocketOption(final String option) { } } + @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 tcpKeepIdle = getExtendedSocketOptionOrNull(TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, callGetter(sock, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = getExtendedSocketOptionOrNull(TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, callGetter(sock, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption 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 tcpKeepIdle = getExtendedSocketOptionOrNull(TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, callGetter(serverSocket, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = getExtendedSocketOptionOrNull(TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, callGetter(serverSocket, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption 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"); }