Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions vertx-core/src/main/asciidoc/tcp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ specifying the port and host of the server, returning a future completed with th
{@link examples.TcpExamples#connectingToAServer}
----

{@link io.vertx.core.net.ConnectOptions} gives more control over the connection, e.g. the local address the
connection is bound to before connecting, when the operating system default is not desired:

[source,$lang]
----
{@link examples.TcpExamples#connectingToAServerFromALocalAddress}
----

=== Making connections to Unix domain sockets

When running on JDK 16+, or using a <<_native_transports,native transport>>, a client can connect to Unix domain sockets:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, HttpCon
obj.setConnectTimeout(((Number)member.getValue()).longValue());
}
break;
case "localAddress":
if (member.getValue() instanceof JsonObject) {
obj.setLocalAddress(io.vertx.core.net.SocketAddress.fromJson((JsonObject)member.getValue()));
}
break;
}
}
}
Expand Down Expand Up @@ -75,5 +80,8 @@ static void toJson(HttpConnectOptions obj, java.util.Map<String, Object> json) {
json.put("sslOptions", obj.getSslOptions().toJson());
}
json.put("connectTimeout", obj.getConnectTimeout());
if (obj.getLocalAddress() != null) {
json.put("localAddress", obj.getLocalAddress().toJson());
}
}
}
19 changes: 19 additions & 0 deletions vertx-core/src/main/java/examples/TcpExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,25 @@ public void connectingToAServer(Vertx vertx) {
});
}

public void connectingToAServerFromALocalAddress(Vertx vertx) {

NetClient client = vertx.createNetClient();
client
.connect(new ConnectOptions()
.setHost("localhost")
.setPort(4321)
// Bind the connection to this network interface, with an ephemeral port
.setLocalAddress(SocketAddress.inetSocketAddress(0, "192.168.0.10")))
.onComplete(res -> {
if (res.succeeded()) {
NetSocket socket = res.result();
System.out.println("Connected from " + socket.localAddress());
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
}

public void configurationOfTcpClientReconnect(Vertx vertx) {

TcpClientConfig options = new TcpClientConfig().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,20 @@ public class HttpConnectOptions {
*/
public static final long DEFAULT_CONNECT_TIMEOUT = -1L;

/**
* The default local address = {@code null} (client default)
*/
public static final SocketAddress DEFAULT_LOCAL_ADDRESS = null;

private HttpVersion protocolVersion;
private ProxyOptions proxyOptions;
private Address server;
private String host;
private Integer port;
private Boolean ssl;
private ClientSSLOptions sslOptions;;
private ClientSSLOptions sslOptions;
private long connectTimeout;
private SocketAddress localAddress;

/**
* Default constructor
Expand All @@ -98,6 +104,7 @@ public HttpConnectOptions(HttpConnectOptions other) {
setSsl(other.ssl);
sslOptions = other.sslOptions != null ? new ClientSSLOptions(other.sslOptions) : null;
setConnectTimeout(other.connectTimeout);
setLocalAddress(other.localAddress);
}

/**
Expand All @@ -123,6 +130,7 @@ protected void init() {
ssl = DEFAULT_SSL;
sslOptions = null;
connectTimeout = DEFAULT_CONNECT_TIMEOUT;
localAddress = DEFAULT_LOCAL_ADDRESS;
}

/**
Expand Down Expand Up @@ -296,6 +304,33 @@ private URL parseUrl(String surl) {
}
}

/**
* Get the local address to bind the connection to, when none is provided the client default local address
* is used and when the client does not define one, the operating system chooses the local address.
*
* @return the local address
*/
public SocketAddress getLocalAddress() {
return localAddress;
}

/**
* Set the local address to bind the connection to.
*
* <p> When set, the connection is bound to this address before connecting to the server, an ephemeral
* port is used when the port is {@code 0}. This overrides the default local address of the client, if any.
*
* @param localAddress the local address, must be an inet socket address
* @return a reference to this, so the API can be used fluently
*/
public HttpConnectOptions setLocalAddress(SocketAddress localAddress) {
if (localAddress != null && localAddress.isDomainSocket()) {
throw new IllegalArgumentException("Cannot set a domain socket local address");
}
this.localAddress = localAddress;
return this;
}

public JsonObject toJson() {
JsonObject json = new JsonObject();
HttpConnectOptionsConverter.toJson(this, json);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ public HttpClientInternal build() {
.protocol("http")
.sslOptions(sslOptions)
.sslEngineOptions(sslEngineOptions)
// Legacy client options may define a default local address
.localAddress(clientOptions != null ? NetClientBuilder.localAddress(clientOptions) : null)
.build();
LogConfig logConfig = co.getTcpConfig().getLogConfig();
ObservabilityConfig observabilityConfig = co.getObservabilityConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ private Future<io.vertx.core.http.HttpClientConnection> connect(HttpClientTransp
Boolean ssl = connect.isSsl();
boolean useSSL = ssl != null ? ssl : defaultSsl;
checkClosed();
HttpConnectParams params = new HttpConnectParams(protocols, sslOptions, proxyOptions, useSSL);
HttpConnectParams params = new HttpConnectParams(protocols, sslOptions, proxyOptions, useSSL, false, connect.getLocalAddress());
return transport.connect(vertx.getOrCreateContext(), server, authority, params, clientMetrics)
.map(conn -> new UnpooledHttpClientConnection(conn).init());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.ClientSSLOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.SocketAddress;

import java.util.List;

Expand All @@ -20,11 +21,21 @@ public HttpConnectParams(List<HttpVersion> protocols,
ProxyOptions proxyOptions,
boolean ssl,
boolean forwardProxy) {
this(protocols, sslOptions, proxyOptions, ssl, forwardProxy, null);
}

public HttpConnectParams(List<HttpVersion> protocols,
ClientSSLOptions sslOptions,
ProxyOptions proxyOptions,
boolean ssl,
boolean forwardProxy,
SocketAddress localAddress) {
this.protocols = protocols;
this.sslOptions = sslOptions;
this.proxyOptions = proxyOptions;
this.ssl = ssl;
this.forwardProxy = forwardProxy;
this.localAddress = localAddress;
}

public final List<HttpVersion> protocols;
Expand All @@ -35,4 +46,9 @@ public HttpConnectParams(List<HttpVersion> protocols,

public final boolean forwardProxy;

/**
* The local address to bind the connection to, {@code null} to use the client default local address.
*/
public final SocketAddress localAddress;

}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ private void connect(ContextInternal context, HttpConnectParams params, HostAndP

ConnectOptions connectOptions = new ConnectOptions();
connectOptions.setRemoteAddress(server);
connectOptions.setLocalAddress(params.localAddress);
if (proxyHttps) {
// Forward mode through an HTTPS proxy: the socket's TLS terminates at the proxy, so the peer /
// SNI is the proxy (the server), not the origin authority.
Expand Down
1 change: 1 addition & 0 deletions vertx-core/src/main/java/io/vertx/core/impl/VertxImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ private WebSocketClientImpl createWebSocketClientImpl(WebSocketClientOptions opt
.protocol("http")
.sslOptions(options.getSslOptions())
.sslEngineOptions(options.getSslEngineOptions())
.localAddress(NetClientBuilder.localAddress(options))
.build();
TcpHttpClientTransport channelConnector = TcpHttpClientTransport.create(tcpClient, config, false, httpMetrics);
return new WebSocketClientImpl(this, o, options, channelConnector, httpMetrics);
Expand Down
30 changes: 30 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/net/ConnectOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ConnectOptions {
private Integer port;
private String sniServerName;
private SocketAddress remoteAddress;
private SocketAddress localAddress;
private ProxyOptions proxyOptions;
private boolean ssl;
private ClientSSLOptions sslOptions;
Expand All @@ -42,6 +43,7 @@ public ConnectOptions() {
port = null;
sniServerName = null;
remoteAddress = null;
localAddress = null;
proxyOptions = null;
ssl = DEFAULT_SSL;
sslOptions = null;
Expand All @@ -58,6 +60,7 @@ public ConnectOptions(ConnectOptions other) {
port = other.getPort();
sniServerName = other.getSniServerName();
remoteAddress = other.getRemoteAddress();
localAddress = other.getLocalAddress();
proxyOptions = other.getProxyOptions() != null ? new ProxyOptions(other.getProxyOptions()) : null;
ssl = other.isSsl();
sslOptions = other.getSslOptions() != null ? new ClientSSLOptions(other.getSslOptions()) : null;
Expand Down Expand Up @@ -126,6 +129,33 @@ public ConnectOptions setRemoteAddress(SocketAddress remoteAddress) {
return this;
}

/**
* Get the local address to bind the client connection to, when none is provided the client default local address
* is used and when the client does not define one, the operating system chooses the local address.
*
* @return the local address
*/
public SocketAddress getLocalAddress() {
return localAddress;
}

/**
* Set the local address to bind the client connection to.
*
* <p> When set, the connection is bound to this address before connecting to the remote address, an ephemeral
* port is used when the port is {@code 0}. This overrides the default local address of the client, if any.
*
* @param localAddress the local address, must be an inet socket address
* @return a reference to this, so the API can be used fluently
*/
public ConnectOptions setLocalAddress(SocketAddress localAddress) {
if (localAddress != null && localAddress.isDomainSocket()) {
throw new IllegalArgumentException("Cannot set a domain socket local address");
}
this.localAddress = localAddress;
return this;
}

/**
* @return the SNI (server name indication) server name
*/
Expand Down
29 changes: 0 additions & 29 deletions vertx-core/src/main/java/io/vertx/core/net/TcpClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public class TcpClientConfig extends TcpEndpointConfig {
private Duration connectTimeout;
private ProxyOptions proxyOptions;
private List<String> nonProxyHosts;
private SocketAddress localAddress;
private int reconnectAttempts;
private Duration reconnectInterval;

Expand All @@ -38,7 +37,6 @@ public TcpClientConfig() {
this.connectTimeout = Duration.ofMillis(ClientOptionsBase.DEFAULT_CONNECT_TIMEOUT);
this.proxyOptions = null;
this.nonProxyHosts = null;
this.localAddress = null;
this.reconnectAttempts = NetClientOptions.DEFAULT_RECONNECT_ATTEMPTS;
this.reconnectInterval = Duration.ofMillis(NetClientOptions.DEFAULT_RECONNECT_INTERVAL);
}
Expand All @@ -48,17 +46,12 @@ public TcpClientConfig(TcpClientConfig other) {
this.connectTimeout = other.connectTimeout;
this.proxyOptions = other.proxyOptions != null ? new ProxyOptions(other.proxyOptions) : null;
this.nonProxyHosts = other.nonProxyHosts != null ? new ArrayList<>(other.nonProxyHosts) : null;
this.localAddress = other.localAddress;
this.reconnectAttempts = other.reconnectAttempts;
this.reconnectInterval = other.reconnectInterval;
}

public TcpClientConfig(NetClientOptions options) {
this((ClientOptionsBase)options);
String localAddress = options.getLocalAddress();
if (localAddress != null) {
setLocalAddress(SocketAddress.inetSocketAddress(0, localAddress));
}
setReconnectAttempts(options.getReconnectAttempts());
setReconnectInterval(Duration.ofMillis(options.getReconnectInterval()));
}
Expand Down Expand Up @@ -178,28 +171,6 @@ public TcpClientConfig addNonProxyHost(String host) {
return this;
}

/**
* @return the local address to bind for network connections.
*/
public SocketAddress getLocalAddress() {
return localAddress;
}

/**
* Set the local address to bind for network connections. When the local address is null,
* it will pick any local address and a random port, the default local address is null.
*
* @param localAddress the local address
* @return a reference to this, so the API can be used fluently
*/
public TcpClientConfig setLocalAddress(SocketAddress localAddress) {
if (localAddress != null && localAddress.isDomainSocket()) {
throw new IllegalArgumentException("Cannot set a domain socket local address");
}
this.localAddress = localAddress;
return this;
}

/**
* @return the value of reconnect attempts
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
package io.vertx.core.net.impl.tcp;

import io.vertx.core.internal.VertxInternal;
import io.vertx.core.net.ClientOptionsBase;
import io.vertx.core.net.ClientSSLOptions;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.SSLEngineOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.TcpClientConfig;

/**
Expand All @@ -27,6 +29,7 @@ public class NetClientBuilder {
private ClientSSLOptions sslOptions;
private SSLEngineOptions sslEngineOptions;
private boolean registerWriteHandler;
private SocketAddress localAddress;

public NetClientBuilder(VertxInternal vertx, TcpClientConfig config) {
this.vertx = vertx;
Expand All @@ -42,6 +45,23 @@ public NetClientBuilder(VertxInternal vertx, NetClientOptions options) {
this.sslOptions = options.getSslOptions();
this.sslEngineOptions = options.getSslEngineOptions();
this.protocol = null;
this.localAddress = localAddress(options);
}

/**
* @return the client default local address of {@code options}, {@code null} when none is configured
*/
public static SocketAddress localAddress(ClientOptionsBase options) {
String localAddress = options.getLocalAddress();
return localAddress != null ? SocketAddress.inetSocketAddress(0, localAddress) : null;
}

/**
* Set the client default local address, used when connect options do not define one.
*/
public NetClientBuilder localAddress(SocketAddress localAddress) {
this.localAddress = localAddress;
return this;
}

public NetClientBuilder sslOptions(ClientSSLOptions sslOptions) {
Expand All @@ -60,6 +80,6 @@ public NetClientBuilder protocol(String protocol) {
}

public NetClientImpl build() {
return new NetClientImpl(vertx, config, protocol, sslOptions, sslEngineOptions, registerWriteHandler);
return new NetClientImpl(vertx, config, protocol, sslOptions, sslEngineOptions, registerWriteHandler, localAddress);
}
}
Loading