From e890aff3cf45f511cb4806abd0f997cff7952077 Mon Sep 17 00:00:00 2001 From: kkewwei Date: Sat, 10 Aug 2024 17:39:52 +0800 Subject: [PATCH] HTTPCORE-761: support ExtendedSocketOption (#473) --- .../http/impl/bootstrap/HttpRequester.java | 10 ++ .../core5/http/impl/bootstrap/HttpServer.java | 10 ++ .../http/impl/bootstrap/RequestListener.java | 10 ++ .../apache/hc/core5/http/io/SocketConfig.java | 86 ++++++++++++++ .../org/apache/hc/core5/io/SocketSupport.java | 77 +++++++++++++ .../hc/core5/reactor/IOReactorConfig.java | 77 +++++++++++++ .../hc/core5/reactor/SingleCoreIOReactor.java | 30 ++++- .../apache/hc/core5/util/ReflectionUtils.java | 22 +++- .../apache/hc/core5/io/TestSocketSupport.java | 108 ++++++++++++++++++ .../hc/core5/reactor/IOReactorConfigTest.java | 6 + 10 files changed, 430 insertions(+), 6 deletions(-) create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java 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 6083689796..8781f534f9 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 @@ -76,6 +76,7 @@ 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.io.SocketSupport; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.pool.ConnPoolControl; import org.apache.hc.core5.pool.ManagedConnPool; @@ -249,6 +250,15 @@ private HttpClientConnection createConnection(final Socket sock, final HttpHost if (socketConfig.getSndBufSize() > 0) { sock.setSendBufferSize(socketConfig.getSndBufSize()); } + if (this.socketConfig.getTcpKeepIdle() > 0) { + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(sock, SocketSupport.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 8c17d1b62c..5a8deaa2d7 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 @@ -56,6 +56,7 @@ 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.io.SocketSupport; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; @@ -145,6 +146,15 @@ public void start() throws IOException { if (this.socketConfig.getRcvBufSize() > 0) { this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize()); } + if (this.socketConfig.getTcpKeepIdle() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.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 f9b325eaa7..60f543e582 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,7 @@ 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.io.SocketSupport; class RequestListener implements Runnable { @@ -91,6 +92,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) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.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 492bc06859..ee3824c29a 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 int getBacklogSize() { 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 String toString() { .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 static SocketConfig.Builder copy(final SocketConfig config) { .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 static class Builder { 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 static class Builder { 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 Builder setBacklogSize(final int backlogSize) { return this; } + /** + * Determines the time (in seconds) the connection needs to remain idle before TCP starts + * sending keepalive probes. + *

+ * 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; + return this; + } + + /** + * Determines the time (in seconds) between individual keepalive probes. + *

+ * 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; + return this; + } + + /** + * Determines the maximum number of keepalive probes TCP should send before dropping the connection. + *

+ * 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; + return this; + } + /** * The address of the SOCKS proxy to use. */ @@ -354,6 +439,7 @@ public SocketConfig build() { 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/io/SocketSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java new file mode 100644 index 0000000000..4ae7ebb2b2 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java @@ -0,0 +1,77 @@ +/* + * ==================================================================== + * 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 + * . + * + */ + +package org.apache.hc.core5.io; + +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; + +/** + * @since 5.3 + */ +@Internal +public class SocketSupport { + + public static final String TCP_KEEPIDLE = "TCP_KEEPIDLE"; + public static final String TCP_KEEPINTERVAL = "TCP_KEEPINTERVAL"; + public static final String TCP_KEEPCOUNT = "TCP_KEEPCOUNT"; + + @SuppressWarnings("unchecked") + public static SocketOption getExtendedSocketOptionOrNull(final String fieldName) { + try { + final Class extendedSocketOptionsClass = Class.forName("jdk.net.ExtendedSocketOptions"); + final Field field = extendedSocketOptionsClass.getField(fieldName); + return (SocketOption) field.get(null); + } catch (final Exception ignore) { + return null; + } + } + + /** + * object can be ServerSocket or Socket + */ + public static 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 socketOption = getExtendedSocketOptionOrNull(fieldName); + if (socketOption == null) { + throw new UnsupportedOperationException("Extended socket option not supported: " + fieldName); + } + setOptionMethod.invoke(object, socketOption, value); + } catch (final UnsupportedOperationException e) { + throw e; + } catch (final Exception ex) { + throw new IOException("Failure setting extended socket option", ex); + } + } + +} 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 70ef1d7d0b..6232ebcdf9 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 int getBacklogSize() { 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 static Builder copy(final IOReactorConfig config) { .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 static void setDefaultMaxIOThreadCount(final int defaultMaxIOThreadCount) 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 static void setDefaultMaxIOThreadCount(final int defaultMaxIOThreadCount) 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 Builder setBacklogSize(final int backlogSize) { 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 IOReactorConfig build() { 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 95d7643202..4e44155c0b 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.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; @@ -47,6 +48,7 @@ import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; +import org.apache.hc.core5.io.SocketSupport; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; @@ -186,7 +188,7 @@ private void processPendingChannels() throws IOException { 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 +263,8 @@ public Future 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 +280,27 @@ private void prepareSocket(final Socket socket) throws IOException { if (linger >= 0) { socket.setSoLinger(true, linger); } + if (this.reactorConfig.getTcpKeepIdle() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPIDLE, this.reactorConfig.getTcpKeepIdle()); + } + if (this.reactorConfig.getTcpKeepInterval() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPINTERVAL, this.reactorConfig.getTcpKeepInterval()); + } + if (this.reactorConfig.getTcpKeepInterval() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPCOUNT, this.reactorConfig.getTcpKeepCount()); + } + } + + /** + * @since 5.3 + */ + void setExtendedSocketOption(final SocketChannel socketChannel, + final String optionName, final T value) throws IOException { + final SocketOption socketOption = SocketSupport.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 +335,7 @@ private void processPendingConnectionRequests() { 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/ReflectionUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java index fad06bca21..c56deb6a74 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 @@ -45,11 +45,27 @@ public static void callSetter(final Object object, final String setterName, fina } public static T callGetter(final Object object, final String getterName, final Class resultType) { + return callGetter(object, getterName, null, null, resultType); + } + + /** + * @sunce 5.3 + */ + 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); - 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; } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java b/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java new file mode 100644 index 0000000000..00bf41a646 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java @@ -0,0 +1,108 @@ +/* + * ==================================================================== + * 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 + * . + * + */ + +package org.apache.hc.core5.io; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketOption; + +import javax.net.ServerSocketFactory; + +import org.apache.hc.core5.util.ReflectionUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestSocketSupport { + + @Test + public void testGetExtendedSocketOptionOrNull() { + testGetExtendedSocketOption(SocketSupport.TCP_KEEPIDLE); + testGetExtendedSocketOption(SocketSupport.TCP_KEEPINTERVAL); + testGetExtendedSocketOption(SocketSupport.TCP_KEEPCOUNT); + } + + private void testGetExtendedSocketOption(final String option) { + final SocketOption socketOption = SocketSupport.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 (ReflectionUtils.determineJRELevel() > 8 && !isWindows()) { + Assertions.assertNotNull(socketOption); + } + } + + @Test + public void testSetOption() throws IOException { + if (ReflectionUtils.determineJRELevel() > 8 && isWindows() == false) { + { + // test Socket + final Socket sock = new Socket(); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPIDLE, 20); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPINTERVAL, 21); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPCOUNT, 22); + + final SocketOption tcpKeepIdle = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, ReflectionUtils.callGetter(sock, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, ReflectionUtils.callGetter(sock, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepCount = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPCOUNT); + assert tcpKeepCount != null; + Assertions.assertEquals(22, ReflectionUtils.callGetter(sock, "Option", tcpKeepCount, SocketOption.class, Integer.class)); + } + + { + // test ServerSocket + final ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPIDLE, 20); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPINTERVAL, 21); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPCOUNT, 22); + + final SocketOption tcpKeepIdle = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepCount = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPCOUNT); + assert tcpKeepCount != null; + Assertions.assertEquals(22, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepCount, SocketOption.class, Integer.class)); + } + } + } + + public static boolean isWindows() { + return System.getProperty("os.name").contains("Windows"); + } + +} 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 9c4942771b..ff1bb120bf 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 @@ void testCustomIOReactorConfig() { .setSndBufSize(32767) .setRcvBufSize(8192) .setBacklogSize(5) + .setTcpKeepIdle(100) + .setTcpKeepInterval(12) + .setTcpKeepCount(4) .setSocksProxyAddress(new InetSocketAddress(8888)) .setSocksProxyUsername("socksProxyUsername") .setSocksProxyPassword("socksProxyPassword") @@ -64,6 +67,9 @@ void testCustomIOReactorConfig() { 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());