diff --git a/client/src/main/java/org/glassfish/tyrus/client/ClientProperties.java b/client/src/main/java/org/glassfish/tyrus/client/ClientProperties.java index 39062b7f..cf84f8ad 100644 --- a/client/src/main/java/org/glassfish/tyrus/client/ClientProperties.java +++ b/client/src/main/java/org/glassfish/tyrus/client/ClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,7 @@ package org.glassfish.tyrus.client; +import java.net.InetAddress; import java.net.URI; import org.glassfish.tyrus.client.auth.AuthConfig; @@ -284,4 +285,15 @@ public final class ClientProperties { * of masking keys. */ public static final String MASKING_KEY_GENERATOR = "org.glassfish.tyrus.client.maskingKeyGenerator"; -} + + /** + * Property name for defining local binding address for all socket created by the client. The expected value is an instance + * of {@link java.net.InetAddress}. + *

+ * Sample below demonstrates how to use this property: + *

+     *     client.getProperties().put(ClientProperties.SOCKET_BINDING, InetAddress.getByName("127.0.0.1"));
+     * 
+ */ + public static final String SOCKET_BINDING = "org.glassfish.tyrus.client.socketBinding"; +} \ No newline at end of file diff --git a/containers/grizzly-client/src/main/java/org/glassfish/tyrus/container/grizzly/client/GrizzlyClientSocket.java b/containers/grizzly-client/src/main/java/org/glassfish/tyrus/container/grizzly/client/GrizzlyClientSocket.java index 63394177..d3388d59 100644 --- a/containers/grizzly-client/src/main/java/org/glassfish/tyrus/container/grizzly/client/GrizzlyClientSocket.java +++ b/containers/grizzly-client/src/main/java/org/glassfish/tyrus/container/grizzly/client/GrizzlyClientSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,7 @@ package org.glassfish.tyrus.container.grizzly.client; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; @@ -359,7 +360,13 @@ public void handleTimeout() { sharedTransportTimeout, proxyHeaders, grizzlyConnector, sslHandshakeFuture, upgradeRequest)); - connectionGrizzlyFuture = connectorHandler.connect(connectAddress); + InetAddress bindingAddress = Utils.getProperty(properties, ClientProperties.SOCKET_BINDING, InetAddress.class); + + if (bindingAddress == null) { + connectionGrizzlyFuture = connectorHandler.connect(connectAddress); + } else { + connectionGrizzlyFuture = connectorHandler.connect(socketAddress, new InetSocketAddress(bindingAddress, 0)); + } try { final Connection connection = connectionGrizzlyFuture.get(timeoutMs, TimeUnit.MILLISECONDS); diff --git a/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/JdkClientContainer.java b/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/JdkClientContainer.java index f4b6959b..a784ad95 100644 --- a/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/JdkClientContainer.java +++ b/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/JdkClientContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,7 @@ package org.glassfish.tyrus.container.jdk.client; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; @@ -99,6 +100,9 @@ public void openClientSocket(final ClientEndpointConfig cec, final Map jdkConnector = new Callable() { @@ -118,13 +122,19 @@ public Void call() throws DeploymentException { if (secure) { TransportFilter transportFilter = - createTransportFilter(SSL_INPUT_BUFFER_SIZE, finalThreadPoolConfig, containerIdleTimeout); + createTransportFilter(SSL_INPUT_BUFFER_SIZE, + finalThreadPoolConfig, + containerIdleTimeout, + bindingAddress); SslFilter sslFilter = createSslFilter(cec, properties, transportFilter, uri); writeQueue = createTaskQueueFilter(sslFilter); } else { TransportFilter transportFilter = - createTransportFilter(INPUT_BUFFER_SIZE, finalThreadPoolConfig, containerIdleTimeout); + createTransportFilter(INPUT_BUFFER_SIZE, + finalThreadPoolConfig, + containerIdleTimeout, + bindingAddress); writeQueue = createTaskQueueFilter(transportFilter); } @@ -276,9 +286,11 @@ private SslFilter createSslFilter(ClientEndpointConfig cec, Map return sslFilter; } - private TransportFilter createTransportFilter(int sslInputBufferSize, ThreadPoolConfig threadPoolConfig, - Integer containerIdleTimeout) { - return new TransportFilter(sslInputBufferSize, threadPoolConfig, containerIdleTimeout); + private TransportFilter createTransportFilter(int sslInputBufferSize, + ThreadPoolConfig threadPoolConfig, + Integer containerIdleTimeout, + InetAddress bindingAddress) { + return new TransportFilter(sslInputBufferSize, threadPoolConfig, containerIdleTimeout, bindingAddress); } private TaskQueueFilter createTaskQueueFilter(Filter downstreamFilter) { diff --git a/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/TransportFilter.java b/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/TransportFilter.java index 940d992f..6b561383 100644 --- a/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/TransportFilter.java +++ b/containers/jdk-client/src/main/java/org/glassfish/tyrus/container/jdk/client/TransportFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,8 @@ package org.glassfish.tyrus.container.jdk.client; import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannelGroup; @@ -85,6 +87,7 @@ public Thread newThread(Runnable r) { private final int inputBufferSize; private final ThreadPoolConfig threadPoolConfig; private final Integer containerIdleTimeout; + private final InetAddress bindingAddress; private volatile AsynchronousSocketChannel socketChannel; @@ -99,12 +102,18 @@ public Thread newThread(Runnable r) { * @param threadPoolConfig thread pool configuration used for creating thread pool. * @param containerIdleTimeout idle time after which the shared thread pool will be destroyed. If {@code null} * default value will be used. The default value is 30 seconds. + * @param bindingAddress local address to bind sockets ({@link #socketChannel}) when they are created. + * Binding is done only if not {@code null}. */ - TransportFilter(int inputBufferSize, ThreadPoolConfig threadPoolConfig, Integer containerIdleTimeout) { + TransportFilter(int inputBufferSize, + ThreadPoolConfig threadPoolConfig, + Integer containerIdleTimeout, + InetAddress bindingAddress) { super(null); this.inputBufferSize = inputBufferSize; this.threadPoolConfig = threadPoolConfig; this.containerIdleTimeout = containerIdleTimeout; + this.bindingAddress = bindingAddress; } @Override @@ -161,6 +170,9 @@ public void handleConnect(SocketAddress serverAddress, Filter upstreamFilter) { updateThreadPoolConfig(); initializeChannelGroup(); socketChannel = AsynchronousSocketChannel.open(channelGroup); + if (bindingAddress != null) { + socketChannel.bind(new InetSocketAddress(bindingAddress, 0)); + } openedConnections.incrementAndGet(); } } catch (IOException e) { diff --git a/containers/jdk-client/src/test/java/org/glassfish/tyrus/container/jdk/client/SslFilterTest.java b/containers/jdk-client/src/test/java/org/glassfish/tyrus/container/jdk/client/SslFilterTest.java index 0f043fc3..b9665ce1 100644 --- a/containers/jdk-client/src/test/java/org/glassfish/tyrus/container/jdk/client/SslFilterTest.java +++ b/containers/jdk-client/src/test/java/org/glassfish/tyrus/container/jdk/client/SslFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -381,7 +381,7 @@ private Filter openClientSocket(String host, final ByteBuffer readBuffer, final SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(sslConfig.createSSLContext()); sslEngineConfigurator.setHostnameVerifier(customHostnameVerifier); - final TransportFilter transportFilter = new TransportFilter(17_000, ThreadPoolConfig.defaultConfig(), null); + final TransportFilter transportFilter = new TransportFilter(17_000, ThreadPoolConfig.defaultConfig(), null, null); final SslFilter sslFilter = new SslFilter(transportFilter, sslEngineConfigurator, host); // exceptions errors that occur before SSL handshake has finished are thrown from this method