From f505e6d26b1d302b21dec93d4006608fa7212728 Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Thu, 14 Sep 2023 21:49:46 +0200 Subject: [PATCH] Start using options internally --- .../core/net/ConnectOptionsConverter.java | 108 ++++++ .../core/http/impl/HttpChannelConnector.java | 22 +- .../vertx/core/http/impl/HttpClientImpl.java | 6 +- .../io/vertx/core/net/ConnectOptions.java | 275 ++++++++++++++ .../io/vertx/core/net/ConnectOptionsBase.java | 356 ------------------ .../java/io/vertx/core/net/NetClient.java | 10 + .../core/net/impl/CleanableNetClient.java | 9 +- .../io/vertx/core/net/impl/NetClientImpl.java | 153 ++++---- .../core/net/impl/NetClientInternal.java | 24 +- .../io/vertx/core/net/impl/SSLHelper.java | 16 +- .../core/net/impl/SslContextProvider.java | 2 +- .../io/vertx/core/net/impl/TCPServerBase.java | 4 +- src/test/java/io/vertx/core/net/NetTest.java | 10 +- .../java/io/vertx/core/net/SSLHelperTest.java | 10 +- 14 files changed, 531 insertions(+), 474 deletions(-) create mode 100644 src/main/generated/io/vertx/core/net/ConnectOptionsConverter.java create mode 100644 src/main/java/io/vertx/core/net/ConnectOptions.java delete mode 100644 src/main/java/io/vertx/core/net/ConnectOptionsBase.java diff --git a/src/main/generated/io/vertx/core/net/ConnectOptionsConverter.java b/src/main/generated/io/vertx/core/net/ConnectOptionsConverter.java new file mode 100644 index 00000000000..c62b555de42 --- /dev/null +++ b/src/main/generated/io/vertx/core/net/ConnectOptionsConverter.java @@ -0,0 +1,108 @@ +package io.vertx.core.net; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.net.ConnectOptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.net.ConnectOptions} original class using Vert.x codegen. + */ +public class ConnectOptionsConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, ConnectOptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "applicationLayerProtocols": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setApplicationLayerProtocols(list); + } + break; + case "host": + if (member.getValue() instanceof String) { + obj.setHost((String)member.getValue()); + } + break; + case "hostnameVerificationAlgorithm": + if (member.getValue() instanceof String) { + obj.setHostnameVerificationAlgorithm((String)member.getValue()); + } + break; + case "port": + if (member.getValue() instanceof Number) { + obj.setPort(((Number)member.getValue()).intValue()); + } + break; + case "proxyOptions": + if (member.getValue() instanceof JsonObject) { + obj.setProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue())); + } + break; + case "sniServerName": + if (member.getValue() instanceof String) { + obj.setSniServerName((String)member.getValue()); + } + break; + case "ssl": + if (member.getValue() instanceof Boolean) { + obj.setSsl((Boolean)member.getValue()); + } + break; + case "sslOptions": + if (member.getValue() instanceof JsonObject) { + obj.setSslOptions(new io.vertx.core.net.SSLOptions((io.vertx.core.json.JsonObject)member.getValue())); + } + break; + case "trustAll": + if (member.getValue() instanceof Boolean) { + obj.setTrustAll((Boolean)member.getValue()); + } + break; + } + } + } + + static void toJson(ConnectOptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(ConnectOptions obj, java.util.Map json) { + if (obj.getApplicationLayerProtocols() != null) { + JsonArray array = new JsonArray(); + obj.getApplicationLayerProtocols().forEach(item -> array.add(item)); + json.put("applicationLayerProtocols", array); + } + if (obj.getHost() != null) { + json.put("host", obj.getHost()); + } + if (obj.getHostnameVerificationAlgorithm() != null) { + json.put("hostnameVerificationAlgorithm", obj.getHostnameVerificationAlgorithm()); + } + if (obj.getPort() != null) { + json.put("port", obj.getPort()); + } + if (obj.getProxyOptions() != null) { + json.put("proxyOptions", obj.getProxyOptions().toJson()); + } + if (obj.getSniServerName() != null) { + json.put("sniServerName", obj.getSniServerName()); + } + json.put("ssl", obj.isSsl()); + if (obj.getSslOptions() != null) { + json.put("sslOptions", obj.getSslOptions().toJson()); + } + json.put("trustAll", obj.isTrustAll()); + } +} diff --git a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java index eb611155f4c..a692222327f 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java +++ b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java @@ -26,9 +26,7 @@ import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.net.NetSocket; -import io.vertx.core.net.ProxyOptions; -import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.*; import io.vertx.core.net.impl.NetClientInternal; import io.vertx.core.net.impl.NetSocketImpl; import io.vertx.core.net.impl.VertxHandler; @@ -85,7 +83,23 @@ public SocketAddress server() { } private void connect(EventLoopContext context, Promise promise) { - netClient.connectInternal(proxyOptions, server, peerAddress, this.options.isForceSni() ? peerAddress.host() : null, ssl, useAlpn, false, promise, context, 0); + ConnectOptions co = new ConnectOptions(); + co.setServer(server); + if (peerAddress != null) { + co.setHost(peerAddress.host()); + co.setPort(peerAddress.port()); + if (options.isForceSni()) { + co.setSniServerName(peerAddress.hostName()); + } + } + co.setSsl(ssl); + co.setHostnameVerificationAlgorithm(options.isVerifyHost() ? "HTTPS": null); + co.setProxyOptions(proxyOptions); + co.setSslOptions(new SSLOptions(client.sslOptions) + .setUseAlpn(useAlpn) + ); + + netClient.connectInternal(co, promise, context); } public Future wrap(EventLoopContext context, NetSocket so_) { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java index b4b66cc0ff3..3af124f7ca7 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java @@ -132,6 +132,7 @@ public class HttpClientImpl implements HttpClientInternal, MetricsProvider { private Predicate proxyFilter; private volatile Handler connectionHandler; private volatile Function> redirectHandler = DEFAULT_HANDLER; + volatile SSLOptions sslOptions; private final Function contextProvider; public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) { @@ -150,6 +151,7 @@ public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) { break; } } + this.sslOptions = options.getSslOptions(); this.keepAlive = options.isKeepAlive(); this.pipelining = options.isPipelining(); if (!keepAlive && pipelining) { @@ -501,7 +503,9 @@ public Metrics getMetrics() { @Override public Future updateSSLOptions(SSLOptions options) { - return netClient.updateSSLOptions(options); + // Todo should validate first ??? + sslOptions = options; + return Future.succeededFuture(); } @Override diff --git a/src/main/java/io/vertx/core/net/ConnectOptions.java b/src/main/java/io/vertx/core/net/ConnectOptions.java new file mode 100644 index 00000000000..567641d84ed --- /dev/null +++ b/src/main/java/io/vertx/core/net/ConnectOptions.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.core.json.JsonObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + + @DataObject(generateConverter = true, publicConverter = false) +public class ConnectOptions { + + /** + * The default value of whether all servers (SSL/TLS) should be trusted = false + */ + public static final boolean DEFAULT_TRUST_ALL = false; + + /** + * SSL enable by default = false + */ + public static final boolean DEFAULT_SSL = false; + + /** + * Default value to determine hostname verification algorithm hostname verification (for SSL/TLS) = "" + */ + public static final String DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM = ""; + + private String host; + private int port; + private SocketAddress server; + + private ProxyOptions proxyOptions; + + private String hostnameVerificationAlgorithm; + private List applicationLayerProtocols; + private String sniServerName; + private boolean trustAll; + private boolean ssl; + private SSLOptions sslOptions; + + /** + * The default constructor + */ + public ConnectOptions() { + host = null; + port = 0; + server = null; + hostnameVerificationAlgorithm = DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM; + applicationLayerProtocols = null; + sniServerName = null; + trustAll = DEFAULT_TRUST_ALL; + ssl = DEFAULT_SSL; + sslOptions = null; + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public ConnectOptions(ConnectOptions other) { + host = other.getHost(); + port = other.getPort(); + server = other.getServer(); + hostnameVerificationAlgorithm = other.getHostnameVerificationAlgorithm(); + applicationLayerProtocols = other.getApplicationLayerProtocols() != null ? new ArrayList<>(other.getApplicationLayerProtocols()) : null; + sniServerName = other.getSniServerName(); + trustAll = other.isTrustAll(); + ssl = other.isSsl(); + sslOptions = other.getSslOptions() != null ? new SSLOptions(other.getSslOptions()) : null; + } + + /** + * Create options from JSON + * + * @param json the JSON + */ + public ConnectOptions(JsonObject json) { + ConnectOptionsConverter.fromJson(json, this); + } + + /** + * Get the host name to be used by the client connection. + * + * @return the host name + */ + public String getHost() { + return host; + } + + /** + * Set the host name to be used by the client connection. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setHost(String host) { + this.host = host; + return this; + } + + /** + * Get the port to be used by the client connection. + * + * @return the port + */ + public Integer getPort() { + return port; + } + + /** + * Set the port to be used by the client connection. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setPort(Integer port) { + this.port = port; + return this; + } + + /** + * Get the server address to be used by the client connection. + * + * @return the server address + */ + public SocketAddress getServer() { + return server; + } + + /** + * Set the server address to be used by the client connection. + * + *

When the server address is {@code null}, the address will be resolved after the {@code host} + * property by the Vert.x resolver. + * + *

Use this when you want to connect to a specific server address without name resolution. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setServer(SocketAddress server) { + this.server = server; + return this; + } + + /** + * Get proxy options for connections + * + * @return proxy options + */ + public ProxyOptions getProxyOptions() { + return proxyOptions; + } + + /** + * Set proxy options for connections via CONNECT proxy (e.g. Squid) or a SOCKS proxy. + * + * @param proxyOptions proxy options object + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setProxyOptions(ProxyOptions proxyOptions) { + this.proxyOptions = proxyOptions; + return this; + } + + /** + * @return the value of the hostname verification algorithm + */ + + public String getHostnameVerificationAlgorithm() { + return hostnameVerificationAlgorithm; + } + + /** + * Set the hostname verification algorithm interval + * + * @param hostnameVerificationAlgorithm should be HTTPS, LDAPS or an empty String + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setHostnameVerificationAlgorithm(String hostnameVerificationAlgorithm) { + this.hostnameVerificationAlgorithm = hostnameVerificationAlgorithm; + return this; + } + + /** + * @return the list of application-layer protocols send during the Application-Layer Protocol Negotiation. + */ + public List getApplicationLayerProtocols() { + return applicationLayerProtocols; + } + + /** + * Set the list of application-layer protocols to provide to the server during the Application-Layer Protocol Negotiation. + * + * @param protocols the protocols + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setApplicationLayerProtocols(List protocols) { + this.applicationLayerProtocols = protocols; + return this; + } + + public String getSniServerName() { + return sniServerName; + } + + public ConnectOptions setSniServerName(String sniServerName) { + this.sniServerName = sniServerName; + return this; + } + + /** + * + * @return true if all server certificates should be trusted + */ + public boolean isTrustAll() { + return trustAll; + } + + /** + * Set whether all server certificates should be trusted + * + * @param trustAll true if all should be trusted + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setTrustAll(boolean trustAll) { + this.trustAll = trustAll; + return this; + } + + /** + * @return is SSL/TLS enabled? + */ + public boolean isSsl() { + return ssl; + } + + /** + * Set whether SSL/TLS is enabled. + * + * @param ssl true if enabled + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setSsl(boolean ssl) { + this.ssl = ssl; + return this; + } + + public SSLOptions getSslOptions() { + return sslOptions; + } + + public void setSslOptions(SSLOptions sslOptions) { + this.sslOptions = sslOptions; + } + + /** + * Convert to JSON + * + * @return the JSON + */ + public JsonObject toJson() { + JsonObject json = new JsonObject(); + ConnectOptionsConverter.toJson(this, json); + return json; + } +} diff --git a/src/main/java/io/vertx/core/net/ConnectOptionsBase.java b/src/main/java/io/vertx/core/net/ConnectOptionsBase.java deleted file mode 100644 index 66c19d13033..00000000000 --- a/src/main/java/io/vertx/core/net/ConnectOptionsBase.java +++ /dev/null @@ -1,356 +0,0 @@ -package io.vertx.core.net; - -import io.vertx.codegen.annotations.DataObject; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -//@DataObject(generateConverter = true, publicConverter = false) -public class ConnectOptionsBase { - - /** - * The default value of whether all servers (SSL/TLS) should be trusted = false - */ - public static final boolean DEFAULT_TRUST_ALL = false; - - /** - * SSL enable by default = false - */ - public static final boolean DEFAULT_SSL = false; - - /** - * The default value of connect timeout = 60 seconds - */ - public static final int DEFAULT_CONNECT_TIMEOUT = 60; - - /** - * Default idle timeout = 0 - */ - public static final int DEFAULT_IDLE_TIMEOUT = 0; - - /** - * Default read idle timeout = 0 - */ - public static final int DEFAULT_READ_IDLE_TIMEOUT = 0; - - /** - * Default write idle timeout = 0 - */ - public static final int DEFAULT_WRITE_IDLE_TIMEOUT = 0; - - /** - * Default value to determine hostname verification algorithm hostname verification (for SSL/TLS) = "" - */ - public static final String DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM = ""; - - /** - * Default time out unit = SECONDS - */ - public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; - - private String host; - private int port; - private SocketAddress server; - - private String hostnameVerificationAlgorithm; - private List applicationLayerProtocols; - private String sniServerName; - private boolean trustAll; - private boolean ssl; - private SSLOptions sslOptions; - - private int connectTimeout; - private int idleTimeout; - private int readIdleTimeout; - private int writeIdleTimeout; - private TimeUnit timeoutUnit; - - public ConnectOptionsBase() { - - host = null; - port = 0; - server = null; - - hostnameVerificationAlgorithm = DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM; - applicationLayerProtocols = null; - sniServerName = null; - trustAll = DEFAULT_TRUST_ALL; - ssl = DEFAULT_SSL; - sslOptions = null; - - connectTimeout = DEFAULT_CONNECT_TIMEOUT; - idleTimeout = DEFAULT_IDLE_TIMEOUT; - readIdleTimeout = DEFAULT_READ_IDLE_TIMEOUT; - writeIdleTimeout = DEFAULT_WRITE_IDLE_TIMEOUT; - timeoutUnit = DEFAULT_TIMEOUT_UNIT; - } - - /** - * Get the host name to be used by the client connection. - * - * @return the host name - */ - public String getHost() { - return host; - } - - /** - * Set the host name to be used by the client connection. - * - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setHost(String host) { - this.host = host; - return this; - } - - /** - * Get the port to be used by the client connection. - * - * @return the port - */ - public Integer getPort() { - return port; - } - - /** - * Set the port to be used by the client connection. - * - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setPort(Integer port) { - this.port = port; - return this; - } - - /** - * Get the server address to be used by the client connection. - * - * @return the server address - */ - public SocketAddress getServer() { - return server; - } - - /** - * Set the server address to be used by the client connection. - * - *

When the server address is {@code null}, the address will be resolved after the {@code host} - * property by the Vert.x resolver. - * - *

Use this when you want to connect to a specific server address without name resolution. - * - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setServer(SocketAddress server) { - this.server = server; - return this; - } - - /** - * @return the value of the hostname verification algorithm - */ - - public String getHostnameVerificationAlgorithm() { - return hostnameVerificationAlgorithm; - } - - /** - * Set the hostname verification algorithm interval - * To disable hostname verification, set hostnameVerificationAlgorithm to an empty String - * - * @param hostnameVerificationAlgorithm should be HTTPS, LDAPS or an empty String - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setHostnameVerificationAlgorithm(String hostnameVerificationAlgorithm) { - Objects.requireNonNull(hostnameVerificationAlgorithm, "hostnameVerificationAlgorithm can not be null!"); - this.hostnameVerificationAlgorithm = hostnameVerificationAlgorithm; - return this; - } - - /** - * @return the list of application-layer protocols send during the Application-Layer Protocol Negotiation. - */ - public List getApplicationLayerProtocols() { - return applicationLayerProtocols; - } - - /** - * Set the list of application-layer protocols to provide to the server during the Application-Layer Protocol Negotiation. - * - * @param protocols the protocols - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setApplicationLayerProtocols(List protocols) { - this.applicationLayerProtocols = protocols; - return this; - } - - public String getSniServerName() { - return sniServerName; - } - - public ConnectOptionsBase setSniServerName(String sniServerName) { - this.sniServerName = sniServerName; - return this; - } - - /** - * - * @return true if all server certificates should be trusted - */ - public boolean isTrustAll() { - return trustAll; - } - - /** - * Set whether all server certificates should be trusted - * - * @param trustAll true if all should be trusted - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setTrustAll(boolean trustAll) { - this.trustAll = trustAll; - return this; - } - - /** - * @return is SSL/TLS enabled? - */ - public boolean isSsl() { - return ssl; - } - - /** - * Set whether SSL/TLS is enabled. - * - * @param ssl true if enabled - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setSsl(boolean ssl) { - this.ssl = ssl; - return this; - } - - public SSLOptions getSslOptions() { - return sslOptions; - } - - public void setSslOptions(SSLOptions sslOptions) { - this.sslOptions = sslOptions; - } - - /** - * @return the value of connect timeout - */ - public int getConnectTimeout() { - return connectTimeout; - } - - /** - * Set the connect timeout in seconds. - *

- * If you want change default time unit, use {@link #setTimeoutUnit(TimeUnit)} - * - * @param connectTimeout connect timeout, in seconds - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setConnectTimeout(int connectTimeout) { - if (connectTimeout < 0) { - throw new IllegalArgumentException("connectTimeout must be >= 0"); - } - this.connectTimeout = connectTimeout; - return this; - } - - /** - * Set the idle timeout, default time unit is seconds. Zero means don't timeout. - * This determines if a connection will timeout and be closed if no data is received nor sent within the timeout. - *

- * If you want change default time unit, use {@link #setTimeoutUnit(TimeUnit)} - * - * @param idleTimeout the timeout - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setIdleTimeout(int idleTimeout) { - if (idleTimeout < 0) { - throw new IllegalArgumentException("idleTimeout must be >= 0"); - } - this.idleTimeout = idleTimeout; - return this; - } - - /** - * @return the idle timeout, in time unit specified by {@link #getIdleTimeoutUnit()}. - */ - public int getIdleTimeout() { - return idleTimeout; - } - - /** - * Set the read idle timeout, default time unit is seconds. Zero means don't timeout. - * This determines if a connection will timeout and be closed if no data is received within the timeout. - *

- * If you want change default time unit, use {@link #setTimeoutUnit(TimeUnit)} - * - * @param idleTimeout the read timeout - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setReadIdleTimeout(int idleTimeout) { - if (idleTimeout < 0) { - throw new IllegalArgumentException("readIdleTimeout must be >= 0"); - } - this.readIdleTimeout = idleTimeout; - return this; - } - - /** - * @return the read idle timeout, in time unit specified by {@link #getTimeoutUnit()}. - */ - public int getReadIdleTimeout() { - return readIdleTimeout; - } - - /** - * Set the write idle timeout, default time unit is seconds. Zero means don't timeout. - * This determines if a connection will timeout and be closed if no data is sent within the timeout. - *

- * If you want change default time unit, use {@link #setTimeoutUnit(TimeUnit)} - * - * @param idleTimeout the write timeout - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setWriteIdleTimeout(int idleTimeout) { - if (idleTimeout < 0) { - throw new IllegalArgumentException("writeIdleTimeout must be >= 0"); - } - this.writeIdleTimeout = idleTimeout; - return this; - } - - /** - * @return the write idle timeout, in time unit specified by {@link #getTimeoutUnit()}. - */ - public int getWriteIdleTimeout() { - return writeIdleTimeout; - } - - /** - * Set the timeout unit. If not specified, default is seconds. - * - * @param timeoutUnit specify time unit. - * @return a reference to this, so the API can be used fluently - */ - public ConnectOptionsBase setTimeoutUnit(TimeUnit timeoutUnit) { - this.timeoutUnit = timeoutUnit; - return this; - } - - /** - * @return the timeout unit. - */ - public TimeUnit getTimeoutUnit() { - return timeoutUnit; - } -} diff --git a/src/main/java/io/vertx/core/net/NetClient.java b/src/main/java/io/vertx/core/net/NetClient.java index 8e93ba5e701..ea39db49674 100644 --- a/src/main/java/io/vertx/core/net/NetClient.java +++ b/src/main/java/io/vertx/core/net/NetClient.java @@ -76,6 +76,16 @@ public interface NetClient extends Measured { */ Future connect(SocketAddress remoteAddress, String serverName); + /** + * Open a connection to a server at the specific {@code connectOptions}. + *

+ * The connect is done asynchronously and on success, a {@link NetSocket} instance is supplied via the {@code connectHandler} instance + * + * @param connectOptions the options describing how to connect to the remote server + * @return a future notified when the socket is connected + */ + Future connect(ConnectOptions connectOptions); + /** * Close the client. *

diff --git a/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java b/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java index 0f3944d18ca..1eefc829990 100644 --- a/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java +++ b/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java @@ -74,6 +74,11 @@ public Future connect(SocketAddress remoteAddress, String serverName) return client.connect(remoteAddress, serverName); } + @Override + public Future connect(ConnectOptions connectOptions) { + return client.connect(connectOptions); + } + @Override public Future close() { action.timeout = 0L; @@ -93,8 +98,8 @@ public void close(Promise completion) { } @Override - public void connectInternal(ProxyOptions proxyOptions, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, boolean registerWriteHandlers, Promise connectHandler, ContextInternal context, int remainingAttempts) { - client.connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts); + public void connectInternal(ConnectOptions connectOptions, Promise connectHandler, ContextInternal context) { + client.connectInternal(connectOptions, connectHandler, context); } @Override diff --git a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java index 70f10f160a2..147fcf481d1 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java @@ -37,7 +37,6 @@ import java.net.ConnectException; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; /** @@ -115,20 +114,40 @@ public Future connect(int port, String host, String serverName) { @Override public Future connect(SocketAddress remoteAddress) { - return connect(remoteAddress, (String) null); + return connect(remoteAddress, null); } @Override public Future connect(SocketAddress remoteAddress, String serverName) { - return connect(vertx.getOrCreateContext(), remoteAddress, serverName); + ConnectOptions connectOptions = new ConnectOptions(); + connectOptions.setServer(remoteAddress); + String peerHost = remoteAddress.host(); + if (peerHost != null && peerHost.endsWith(".")) { + peerHost= peerHost.substring(0, peerHost.length() - 1); + } + if (peerHost != null) { + connectOptions.setHost(peerHost); + connectOptions.setPort(remoteAddress.port()); + } + connectOptions.setSniServerName(serverName); + connectOptions.setHostnameVerificationAlgorithm(options.getHostnameVerificationAlgorithm()); + connectOptions.setSsl(options.isSsl()); + return connect(connectOptions); } - public Future connect(ContextInternal context, SocketAddress remoteAddress, String serverName) { + @Override + public Future connect(ConnectOptions connectOptions) { + ContextInternal context = vertx.getOrCreateContext(); Promise promise = context.promise(); - connect(remoteAddress, serverName, promise, context); + connectInternal(connectOptions, options.isRegisterWriteHandler(), promise, context, options.getReconnectAttempts()); return promise.future(); } + @Override + public void connectInternal(ConnectOptions connectOptions, Promise connectHandler, ContextInternal context) { + connectInternal(connectOptions, false, connectHandler, context, 0); + } + private void doShutdown(Promise p) { if (closeEvent == null) { closeEvent = new ShutdownEvent(0, TimeUnit.SECONDS); @@ -210,60 +229,29 @@ public Future updateSSLOptions(SSLOptions options) { return ctx.succeededFuture(); } - private void connect(SocketAddress remoteAddress, String serverName, Promise connectHandler, ContextInternal ctx) { - if (closeSequence.started()) { - throw new IllegalStateException("Client is closed"); - } - SocketAddress peerAddress = remoteAddress; - String peerHost = peerAddress.host(); - if (peerHost != null && peerHost.endsWith(".")) { - peerAddress = SocketAddress.inetSocketAddress(peerAddress.port(), peerHost.substring(0, peerHost.length() - 1)); - } - ProxyOptions proxyOptions = options.getProxyOptions(); - if (proxyFilter != null) { - if (!proxyFilter.test(remoteAddress)) { - proxyOptions = null; - } - } - connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, options.isSsl(), options.isUseAlpn(), options.isRegisterWriteHandler(), connectHandler, ctx, options.getReconnectAttempts()); - } - - /** - * Open a socket to the {@code remoteAddress} server. - * - * @param proxyOptions optional proxy configuration - * @param remoteAddress the server address - * @param peerAddress the peer address (along with SSL) - * @param serverName the SNI server name (along with SSL) - * @param ssl whether to use SSL - * @param useAlpn wether to use ALPN (along with SSL) - * @param registerWriteHandlers whether to register event-bus write handlers - * @param connectHandler the promise to resolve with the connect result - * @param context the socket context - * @param remainingAttempts how many times reconnection is reattempted - */ - public void connectInternal(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - String serverName, - boolean ssl, - boolean useAlpn, - boolean registerWriteHandlers, - Promise connectHandler, - ContextInternal context, - int remainingAttempts) { + private void connectInternal(ConnectOptions connectOptions, + boolean registerWriteHandlers, + Promise connectHandler, + ContextInternal context, + int remainingAttempts) { if (closeSequence.started()) { connectHandler.fail(new IllegalStateException("Client is closed")); } else { - SSLOptions sslOptions; - Future fut; - synchronized (NetClientImpl.this) { - sslOptions = this.sslOptions; + SSLOptions sslOptions = connectOptions.getSslOptions(); + if (sslOptions == null) { + synchronized (NetClientImpl.this) { + sslOptions = this.sslOptions; + } + if (sslOptions == null) { + sslOptions = new SSLOptions(); // DO WE NEED THIS ??? AVOID NPE + } } - fut = sslHelper.resolveSslChannelProvider(sslOptions, useAlpn, options.isTrustAll(), context); + SSLOptions bilto = sslOptions; + Future fut; + fut = sslHelper.resolveSslChannelProvider(sslOptions, connectOptions.getHostnameVerificationAlgorithm(), bilto.isUseAlpn(), connectOptions.isTrustAll(), context); fut.onComplete(ar -> { if (ar.succeeded()) { - connectInternal2(proxyOptions, remoteAddress, peerAddress, ar.result(), serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts); + connectInternal2(connectOptions, bilto, ar.result(), registerWriteHandlers, connectHandler, context, remainingAttempts); } else { connectHandler.fail(ar.cause()); } @@ -271,17 +259,13 @@ public void connectInternal(ProxyOptions proxyOptions, } } - private void connectInternal2(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - SslChannelProvider sslChannelProvider, - String serverName, - boolean ssl, - boolean useAlpn, - boolean registerWriteHandlers, - Promise connectHandler, - ContextInternal context, - int remainingAttempts) { + private void connectInternal2(ConnectOptions connectOptions, + SSLOptions sslOptions, + SslChannelProvider sslChannelProvider, + boolean registerWriteHandlers, + Promise connectHandler, + ContextInternal context, + int remainingAttempts) { EventLoop eventLoop = context.nettyEventLoop(); if (eventLoop.inEventLoop()) { @@ -290,14 +274,45 @@ private void connectInternal2(ProxyOptions proxyOptions, bootstrap.group(eventLoop); bootstrap.option(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); + SocketAddress remoteAddress = connectOptions.getServer(); + if (remoteAddress == null) { + String host = connectOptions.getHost(); + Integer port = connectOptions.getPort(); + if (host == null || port == null) { + throw new UnsupportedOperationException("handle me"); + } + remoteAddress = SocketAddress.inetSocketAddress(port, host); + } + String peerHost = connectOptions.getHost(); + Integer peerPort = connectOptions.getPort(); + if (remoteAddress.isInetSocket()) { + if (peerHost == null) { + peerHost = remoteAddress.host();; + } + if (peerPort == null) { + peerPort = remoteAddress.port(); + } + } + vertx.transport().configure(options, remoteAddress.isDomainSocket(), bootstrap); + ProxyOptions proxyOptions = connectOptions.getProxyOptions(); + if (proxyOptions == null) { + proxyOptions = options.getProxyOptions(); + } + if (proxyFilter != null) { + if (!proxyFilter.test(remoteAddress)) { + proxyOptions = null; + } + } + ChannelProvider channelProvider = new ChannelProvider(bootstrap, sslChannelProvider, context) .proxyOptions(proxyOptions); - channelProvider.handler(ch -> connected(context, ch, connectHandler, remoteAddress, sslChannelProvider, channelProvider.applicationProtocol(), registerWriteHandlers)); - - io.netty.util.concurrent.Future fut = channelProvider.connect(remoteAddress, peerAddress, serverName, ssl, useAlpn, options.isTrustAll(), options.getSslHandshakeTimeout(), options.getSslHandshakeTimeoutUnit()); + SocketAddress foo = remoteAddress; + SocketAddress peerAddress = peerHost != null && peerPort != null ? SocketAddress.inetSocketAddress(peerPort, peerHost) : null; + channelProvider.handler(ch -> connected(context, ch, connectHandler, foo, sslChannelProvider, channelProvider.applicationProtocol(), registerWriteHandlers)); + io.netty.util.concurrent.Future fut = channelProvider.connect(remoteAddress, peerAddress, connectOptions.getSniServerName(), connectOptions.isSsl(), sslOptions.isUseAlpn(), options.isTrustAll(), options.getSslHandshakeTimeout(), options.getSslHandshakeTimeoutUnit()); fut.addListener((GenericFutureListener>) future -> { if (!future.isSuccess()) { Throwable cause = future.cause(); @@ -308,7 +323,7 @@ private void connectInternal2(ProxyOptions proxyOptions, log.debug("Failed to create connection. Will retry in " + options.getReconnectInterval() + " milliseconds"); //Set a timer to retry connection vertx.setTimer(options.getReconnectInterval(), tid -> - connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts == -1 ? remainingAttempts : remainingAttempts - 1) + connectInternal(connectOptions, registerWriteHandlers, connectHandler, context, remainingAttempts == -1 ? remainingAttempts : remainingAttempts - 1) ); }); } else { @@ -317,7 +332,7 @@ private void connectInternal2(ProxyOptions proxyOptions, } }); } else { - eventLoop.execute(() -> connectInternal2(proxyOptions, remoteAddress, peerAddress, sslChannelProvider, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts)); + eventLoop.execute(() -> connectInternal2(connectOptions, sslOptions, sslChannelProvider, registerWriteHandlers, connectHandler, context, remainingAttempts)); } } diff --git a/src/main/java/io/vertx/core/net/impl/NetClientInternal.java b/src/main/java/io/vertx/core/net/impl/NetClientInternal.java index e525fb14df0..1632573d1aa 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientInternal.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientInternal.java @@ -24,27 +24,13 @@ public interface NetClientInternal extends NetClient, MetricsProvider, Closeable /** * Open a socket to the {@code remoteAddress} server. * - * @param proxyOptions optional proxy configuration - * @param remoteAddress the server address - * @param peerAddress the peer address (along with SSL) - * @param serverName the SNI server name (along with SSL) - * @param ssl whether to use SSL - * @param useAlpn wether to use ALPN (along with SSL) - * @param registerWriteHandlers whether to register event-bus write handlers + * @param connectOptions the connect options * @param connectHandler the promise to resolve with the connect result - * @param context the socket context - * @param remainingAttempts how many times reconnection is reattempted + * @param context the socket context */ - void connectInternal(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - String serverName, - boolean ssl, - boolean useAlpn, - boolean registerWriteHandlers, - Promise connectHandler, - ContextInternal context, - int remainingAttempts); + void connectInternal(ConnectOptions connectOptions, + Promise connectHandler, + ContextInternal context); @Override default Future close() { diff --git a/src/main/java/io/vertx/core/net/impl/SSLHelper.java b/src/main/java/io/vertx/core/net/impl/SSLHelper.java index bb1af2f9827..c26bebb5b8c 100755 --- a/src/main/java/io/vertx/core/net/impl/SSLHelper.java +++ b/src/main/java/io/vertx/core/net/impl/SSLHelper.java @@ -112,7 +112,6 @@ public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptio private final boolean sni; private final ClientAuth clientAuth; private final boolean client; - private final String endpointIdentificationAlgorithm; private final List applicationProtocols; private final Supplier supplier; private final boolean useWorkerPool; @@ -124,17 +123,16 @@ public SSLHelper(TCPSSLOptions options, List applicationProtocols) { SSLEngineOptions resolvedEngineOptions = resolveEngineOptions(sslEngineOptions, options.isUseAlpn()); this.client = options instanceof ClientOptionsBase; this.clientAuth = options instanceof NetServerOptions ? ((NetServerOptions)options).getClientAuth() : ClientAuth.NONE; - this.endpointIdentificationAlgorithm = options instanceof NetClientOptions ? ((NetClientOptions)options).getHostnameVerificationAlgorithm() : ""; this.sni = options instanceof NetServerOptions && ((NetServerOptions) options).isSni(); this.applicationProtocols = applicationProtocols; this.supplier = resolvedEngineOptions::sslContextFactory; this.useWorkerPool = resolvedEngineOptions.getUseWorkerThread(); } - public Future resolveSslChannelProvider(SSLOptions options, boolean useAlpn, boolean trustAll, ContextInternal ctx) { + public Future resolveSslChannelProvider(SSLOptions options, String endpointIdentificationAlgorithm, boolean useAlpn, boolean trustAll, ContextInternal ctx) { // return buildChannelProvider(options, ctx); // Two level caching ... for now - return sslChannelProviderMap.computeIfAbsent(options, o -> buildChannelProvider(options, trustAll, ctx)); + return sslChannelProviderMap.computeIfAbsent(options, o -> buildChannelProvider(options, endpointIdentificationAlgorithm, trustAll, ctx)); } /** @@ -143,11 +141,11 @@ public Future resolveSslChannelProvider(SSLOptions options, * @param ctx the context * @return a future resolved when the helper is initialized */ - public Future buildContextProvider(SSLOptions sslOptions, ContextInternal ctx) { - return buildConfig(sslOptions, ctx).map(config -> buildSslContextProvider(sslOptions, supplier, config)); + public Future buildContextProvider(SSLOptions sslOptions, String endpointIdentificationAlgorithm, ContextInternal ctx) { + return buildConfig(sslOptions, ctx).map(config -> buildSslContextProvider(sslOptions, endpointIdentificationAlgorithm, supplier, config)); } - private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, Supplier supplier, Config config) { + private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, String endpointIdentificationAlgorithm, Supplier supplier, Config config) { return new SslContextProvider( clientAuth, endpointIdentificationAlgorithm, @@ -168,10 +166,10 @@ private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, Suppli * @param ctx the context * @return a future resolved when the helper is initialized */ - public Future buildChannelProvider(SSLOptions sslOptions, boolean trustAll, ContextInternal ctx) { + public Future buildChannelProvider(SSLOptions sslOptions, String endpointIdentificationAlgorithm, boolean trustAll, ContextInternal ctx) { Future f; boolean useWorker; - f = buildConfig(sslOptions, ctx).map(config -> buildSslContextProvider(sslOptions, supplier, config)); + f = buildConfig(sslOptions, ctx).map(config -> buildSslContextProvider(sslOptions, endpointIdentificationAlgorithm, supplier, config)); useWorker = useWorkerPool; return f.map(c -> new SslChannelProvider( c, diff --git a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java b/src/main/java/io/vertx/core/net/impl/SslContextProvider.java index f60c0ce369b..0827f454a54 100644 --- a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java +++ b/src/main/java/io/vertx/core/net/impl/SslContextProvider.java @@ -254,7 +254,7 @@ public void configureEngine(SSLEngine engine, Set enabledProtocols, Stri Set protocols = new LinkedHashSet<>(enabledProtocols); protocols.retainAll(Arrays.asList(engine.getSupportedProtocols())); engine.setEnabledProtocols(protocols.toArray(new String[protocols.size()])); - if (client && !endpointIdentificationAlgorithm.isEmpty()) { + if (client && endpointIdentificationAlgorithm != null && !endpointIdentificationAlgorithm.isEmpty()) { SSLParameters sslParameters = engine.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); engine.setSSLParameters(sslParameters); diff --git a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java index 594f0c41dfa..951fe4c1dfa 100644 --- a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java +++ b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java @@ -131,7 +131,7 @@ public Future updateSSLOptions(SSLOptions options) { Future fut; synchronized (this) { if (updateInProgress == null) { - updateInProgress = sslHelper.resolveSslChannelProvider(options, this.options.isUseAlpn(), false, ctx); + updateInProgress = sslHelper.resolveSslChannelProvider(options, null, this.options.isUseAlpn(), false, ctx); fut = updateInProgress; } else { return updateInProgress.mapEmpty().transform(ar -> updateSSLOptions(options)); @@ -214,7 +214,7 @@ private synchronized Future listen(SocketAddress localAddress, ContextI listenContext.addCloseHook(this); // Initialize SSL before binding - sslChannelProvider = sslHelper.resolveSslChannelProvider(options.getSslOptions(), options.isUseAlpn(), false, listenContext).onComplete(ar -> { + sslChannelProvider = sslHelper.resolveSslChannelProvider(options.getSslOptions(), null, options.isUseAlpn(), false, listenContext).onComplete(ar -> { if (ar.succeeded()) { // Socket bind diff --git a/src/test/java/io/vertx/core/net/NetTest.java b/src/test/java/io/vertx/core/net/NetTest.java index 4ded4e0e3b2..c79741cba6e 100755 --- a/src/test/java/io/vertx/core/net/NetTest.java +++ b/src/test/java/io/vertx/core/net/NetTest.java @@ -2411,12 +2411,10 @@ public void testCloseTwice() { @Test public void testAttemptConnectAfterClose() { client.close(); - try { - client.connect(testAddress); - fail("Should throw exception"); - } catch (IllegalStateException e) { - //OK - } + client.connect(testAddress).onComplete(onFailure(err -> { + testComplete(); + })); + await(); } @Test diff --git a/src/test/java/io/vertx/core/net/SSLHelperTest.java b/src/test/java/io/vertx/core/net/SSLHelperTest.java index add737058f6..2ab918bdcd2 100755 --- a/src/test/java/io/vertx/core/net/SSLHelperTest.java +++ b/src/test/java/io/vertx/core/net/SSLHelperTest.java @@ -44,7 +44,7 @@ public void testUseJdkCiphersWhenNotSpecified() throws Exception { SSLHelper helper = new SSLHelper(new HttpClientOptions().setKeyStoreOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), null); helper - .buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), (ContextInternal) vertx.getOrCreateContext()) + .buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), null, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(provider -> { SslContext ctx = provider.createClientContext(null, false, false); assertEquals(new HashSet<>(Arrays.asList(expected)), new HashSet<>(ctx.cipherSuites())); @@ -59,7 +59,7 @@ public void testUseOpenSSLCiphersWhenNotSpecified() throws Exception { SSLHelper helper = new SSLHelper( new HttpClientOptions().setOpenSslEngineOptions(new OpenSSLEngineOptions()).setPemKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), null); - helper.buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), (ContextInternal) vertx.getOrCreateContext()).onComplete(onSuccess(provider -> { + helper.buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), null, (ContextInternal) vertx.getOrCreateContext()).onComplete(onSuccess(provider -> { SslContext ctx = provider.createClientContext(null, false, false); assertEquals(expected, new HashSet<>(ctx.cipherSuites())); testComplete(); @@ -88,7 +88,7 @@ private void testOpenSslServerSessionContext(boolean testDefault){ null); defaultHelper - .buildContextProvider(httpServerOptions.getSslOptions(), (ContextInternal) vertx.getOrCreateContext()) + .buildContextProvider(httpServerOptions.getSslOptions(), null, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(provider -> { SslContext ctx = provider.createServerContext(false); @@ -119,7 +119,7 @@ public void testPreserveEnabledCipherSuitesOrder() throws Exception { assertEquals(new ArrayList<>(new HttpServerOptions(json).getEnabledCipherSuites()), Arrays.asList(engine.getEnabledCipherSuites())); SSLHelper helper = new SSLHelper(options.setKeyCertOptions(Cert.SERVER_JKS.get()), null); helper - .buildContextProvider(options.getSslOptions(), (ContextInternal) vertx.getOrCreateContext()) + .buildContextProvider(options.getSslOptions(), null, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(sslContextProvider -> { assertEquals(new HashSet<>(Arrays.asList(createEngine(sslContextProvider).getEnabledCipherSuites())), new HashSet<>(Arrays.asList(engine.getEnabledCipherSuites()))); testComplete(); @@ -175,7 +175,7 @@ public void testSetVersions() { private void testTLSVersions(HttpServerOptions options, Consumer check) { SSLHelper helper = new SSLHelper(options.setSsl(true).setKeyCertOptions(Cert.SERVER_JKS.get()), null); helper - .buildContextProvider(options.getSslOptions(), (ContextInternal) vertx.getOrCreateContext()) + .buildContextProvider(options.getSslOptions(), null, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(sslContextProvider -> { SSLEngine engine = createEngine(sslContextProvider); check.accept(engine);