All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.nats.client.Options Maven / Gradle / Ivy

// Copyright 2015-2018 The NATS Authors
// Licensed 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.

package io.nats.client;

import io.nats.client.impl.*;
import io.nats.client.support.HttpRequest;
import io.nats.client.support.NatsConstants;
import io.nats.client.support.NatsUri;
import io.nats.client.support.SSLUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static io.nats.client.support.Encoding.*;
import static io.nats.client.support.NatsConstants.*;
import static io.nats.client.support.SSLUtils.DEFAULT_TLS_ALGORITHM;
import static io.nats.client.support.Validator.*;

/**
 * The Options class specifies the connection options for a new NATs connection, including the default options.
 * Options are created using a {@link Options.Builder Builder}.
 * This class and the builder associated with it, is basically a long list of parameters. The documentation attempts
 * to clarify the value of each parameter in place on the builder and here, but it may be easier to read the documentation
 * starting with the {@link Options.Builder Builder}, since it has a simple list of methods that configure the connection.
 */
public class Options {
    // ----------------------------------------------------------------------------------------------------
    // NOTE TO DEVS!!! To add an option, you have to address:
    // ----------------------------------------------------------------------------------------------------
    // CONSTANTS * optionally add a default value constant
    // ENVIRONMENT PROPERTIES * most of the time add an environment property, should always be in the form PFX +
    // PROTOCOL CONNECT OPTION CONSTANTS * not related to options, but here because Options code uses them
    // CLASS VARIABLES * add a variable to the class
    // BUILDER VARIABLES * add a variable in builder
    // BUILD CONSTRUCTOR PROPS * update build props constructor to read new props
    // BUILDER METHODS * add a chainable method in builder for new variable
    // BUILD IMPL * update build() implementation if needed
    // BUILDER COPY CONSTRUCTOR * update builder constructor to ensure new variables are set
    // CONSTRUCTOR * update constructor to ensure new variables are set from builder
    // GETTERS * update getter to be able to retrieve class variable value
    // HELPER FUNCTIONS * just helpers
    // ----------------------------------------------------------------------------------------------------
    // README - if you add a property or change it's comment, add it to or update the readme
    // ----------------------------------------------------------------------------------------------------

    // ----------------------------------------------------------------------------------------------------
    // CONSTANTS
    // ----------------------------------------------------------------------------------------------------
    /**
     * Default server URL. This property is defined as {@value}
     */
    public static final String DEFAULT_URL = "nats://localhost:4222";

    /**
     * Default server port. This property is defined as {@value}
     */
    public static final int DEFAULT_PORT = NatsConstants.DEFAULT_PORT;

    /**
     * Default maximum number of reconnect attempts, see {@link #getMaxReconnect() getMaxReconnect()}.
     * This property is defined as {@value}
     */
    public static final int DEFAULT_MAX_RECONNECT = 60;

    /**
     * Default wait time before attempting reconnection to the same server, see {@link #getReconnectWait() getReconnectWait()}.
     * This property is defined as 2000 milliseconds (2 seconds).
     */
    public static final Duration DEFAULT_RECONNECT_WAIT = Duration.ofMillis(2000);

    /**
     * Default wait time before attempting reconnection to the same server, see {@link #getReconnectJitter() getReconnectJitter()}.
     * This property is defined as 100 milliseconds.
     */
    public static final Duration DEFAULT_RECONNECT_JITTER = Duration.ofMillis(100);

    /**
     * Default wait time before attempting reconnection to the same server, see {@link #getReconnectJitterTls() getReconnectJitterTls()}.
     * This property is defined as 1000 milliseconds (1 second).
     */
    public static final Duration DEFAULT_RECONNECT_JITTER_TLS = Duration.ofMillis(1000);

    /**
     * Default connection timeout, see {@link #getConnectionTimeout() getConnectionTimeout()}.
     * This property is defined as 2 seconds.
     */
    public static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(2);

    /**
     * Default socket write timeout, see {@link #getSocketWriteTimeout() getSocketWriteTimeout()}.
     * This property is defined as 1 minute
     */
    public static final Duration DEFAULT_SOCKET_WRITE_TIMEOUT = Duration.ofMinutes(1);

    /**
     * Constant used for calculating if a socket write timeout is large enough.
     */
    public static final long MINIMUM_SOCKET_WRITE_TIMEOUT_GT_CONNECTION_TIMEOUT = 100;

    /**
     * Constant used for calculating if a socket read timeout is large enough.
     */
    public static final long MINIMUM_SOCKET_READ_TIMEOUT_GT_CONNECTION_TIMEOUT = 100;

    /**
     * Default server ping interval. The client will send a ping to the server on this interval to insure liveness.
     * The server may send pings to the client as well, these are handled automatically by the library,
     * see {@link #getPingInterval() getPingInterval()}.
     * 

A value of {@code <=0} means disabled.

*

This property is defined as 2 minutes.

*/ public static final Duration DEFAULT_PING_INTERVAL = Duration.ofMinutes(2); /** * Default interval to clean up cancelled/timed out requests. * A timer is used to clean up futures that were handed out but never completed * via a message, {@link #getRequestCleanupInterval() getRequestCleanupInterval()}. *

This property is defined as 5 seconds.

*/ public static final Duration DEFAULT_REQUEST_CLEANUP_INTERVAL = Duration.ofSeconds(5); /** * Default maximum number of pings have not received a response allowed by the * client, {@link #getMaxPingsOut() getMaxPingsOut()}. *

This property is defined as {@value}

*/ public static final int DEFAULT_MAX_PINGS_OUT = 2; /** * Default SSL protocol used to create an SSLContext if the {@link #PROP_SECURE * secure property} is used. *

This property is defined as {@value}

*/ public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2"; /** * Default of pending message buffer that is used for buffering messages that * are published during a disconnect/reconnect, {@link #getReconnectBufferSize() getReconnectBufferSize()}. *

This property is defined as {@value} bytes, 8 * 1024 * 1024.

*/ public static final int DEFAULT_RECONNECT_BUF_SIZE = 8_388_608; /** * The default length, {@value} bytes, the client will allow in an * outgoing protocol control line, {@link #getMaxControlLine() getMaxControlLine()}. *

This value is configurable on the server, and should be set here to match.

*/ public static final int DEFAULT_MAX_CONTROL_LINE = 4096; /** * Default dataport class, which will use a TCP socket, {@link #getDataPortType() getDataPortType()}. *

This option is currently provided only for testing, and experimentation, the default * should be used in almost all cases.

*/ public static final String DEFAULT_DATA_PORT_TYPE = SocketDataPort.class.getCanonicalName(); /** * Default size for buffers in the connection, not as available as other settings, * this is primarily changed for testing, {@link #getBufferSize() getBufferSize()}. */ public static final int DEFAULT_BUFFER_SIZE = 64 * 1024; /** * Default thread name prefix. Used by the default executor when creating threads. * This property is defined as {@value} */ public static final String DEFAULT_THREAD_NAME_PREFIX = "nats"; /** * Default prefix used for inboxes, you can change this to manage authorization of subjects. * See {@link #getInboxPrefix() getInboxPrefix()}, the . is required but will be added if missing. */ public static final String DEFAULT_INBOX_PREFIX = "_INBOX."; /** * This value is used internally to limit the number of messages sent in a single network I/O. * The value returned by {@link #getBufferSize() getBufferSize()} is used first, but if the buffer * size is large and the message sizes are small, this limit comes into play. * The choice of 1000 is arbitrary and based on testing across several operating systems. Use buffer * size for tuning. */ public static final int MAX_MESSAGES_IN_NETWORK_BUFFER = 1000; /** * This value is used internally to limit the number of messages allowed in the outgoing queue. When * this limit is reached, publish requests will be blocked until the queue can clear. * Because this value is in messages, the memory size associated with this value depends on the actual * size of messages. If 0 byte messages are used, then DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE will take up the minimal * space. If 1024 byte messages are used then approximately 5Mb is used for the queue (plus overhead for subjects, etc..) * We are using messages, not bytes, to allow a simplification in the underlying library, and use LinkedBlockingQueue as * the core element in the queue. */ public static final int DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE = 5000; /** * This value is used internally to discard messages when the outgoing queue is full. * See {@link #DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE} */ public static final boolean DEFAULT_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL = false; // ---------------------------------------------------------------------------------------------------- // ENVIRONMENT PROPERTIES // ---------------------------------------------------------------------------------------------------- static final String PFX = "io.nats.client."; static final int PFX_LEN = PFX.length(); /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#connectionListener(ConnectionListener) connectionListener}. */ public static final String PROP_CONNECTION_CB = PFX + "callback.connection"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#dataPortType(String) dataPortType}. */ public static final String PROP_DATA_PORT_TYPE = PFX + "dataport.type"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#errorListener(ErrorListener) errorListener}. */ public static final String PROP_ERROR_LISTENER = PFX + "callback.error"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#timeTraceLogger(TimeTraceLogger) timeTraceLogger}. */ public static final String PROP_TIME_TRACE_LOGGER = PFX + "time.trace"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#statisticsCollector(StatisticsCollector) statisticsCollector}. */ public static final String PROP_STATISTICS_COLLECTOR = PFX + "statisticscollector"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#maxPingsOut(int) maxPingsOut}. */ public static final String PROP_MAX_PINGS = PFX + "maxpings"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#pingInterval(Duration) * pingInterval}. */ public static final String PROP_PING_INTERVAL = PFX + "pinginterval"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#requestCleanupInterval(Duration) * requestCleanupInterval}. */ public static final String PROP_CLEANUP_INTERVAL = PFX + "cleanupinterval"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#connectionTimeout(Duration) connectionTimeout}. */ public static final String PROP_CONNECTION_TIMEOUT = PFX + "timeout"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#socketReadTimeoutMillis(int) socketReadTimeoutMillis}. */ public static final String PROP_SOCKET_READ_TIMEOUT_MS = PFX + "socket.read.timeout.ms"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#socketWriteTimeout(long) socketWriteTimeout}. */ public static final String PROP_SOCKET_WRITE_TIMEOUT = PFX + "socket.write.timeout"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#socketSoLinger(int) socketSoLinger}. */ public static final String PROP_SOCKET_SO_LINGER = PFX + "socket.so.linger"; /** * Property used to configure a builder from a Properties object. {@value}, see * {@link Builder#reconnectBufferSize(long) reconnectBufferSize}. */ public static final String PROP_RECONNECT_BUF_SIZE = PFX + "reconnect.buffer.size"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#reconnectWait(Duration) * reconnectWait}. */ public static final String PROP_RECONNECT_WAIT = PFX + "reconnect.wait"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#maxReconnects(int) * maxReconnects}. */ public static final String PROP_MAX_RECONNECT = PFX + "reconnect.max"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#reconnectJitter(Duration) * reconnectJitter}. */ public static final String PROP_RECONNECT_JITTER = PFX + "reconnect.jitter"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#reconnectJitterTls(Duration) * reconnectJitterTls}. */ public static final String PROP_RECONNECT_JITTER_TLS = PFX + "reconnect.jitter.tls"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#pedantic() pedantic}. */ public static final String PROP_PEDANTIC = PFX + "pedantic"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#verbose() verbose}. */ public static final String PROP_VERBOSE = PFX + "verbose"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#noEcho() noEcho}. */ public static final String PROP_NO_ECHO = PFX + "noecho"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#noHeaders() noHeaders}. */ public static final String PROP_NO_HEADERS = PFX + "noheaders"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#connectionName(String) * connectionName}. */ public static final String PROP_CONNECTION_NAME = PFX + "name"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#noNoResponders() noNoResponders}. */ public static final String PROP_NO_NORESPONDERS = PFX + "nonoresponders"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#noRandomize() noRandomize}. */ public static final String PROP_NORANDOMIZE = PFX + "norandomize"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#noResolveHostnames() noResolveHostnames}. */ public static final String PROP_NO_RESOLVE_HOSTNAMES = PFX + "noResolveHostnames"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#reportNoResponders() reportNoResponders}. */ public static final String PROP_REPORT_NO_RESPONDERS = PFX + "reportNoResponders"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#clientSideLimitChecks() clientSideLimitChecks}. */ public static final String PROP_CLIENT_SIDE_LIMIT_CHECKS = PFX + "clientsidelimitchecks"; /** * Property used to configure a builder from a Properties object. {@value}, * see {@link Builder#servers(String[]) servers}. The value can be a comma-separated list of server URLs. */ public static final String PROP_SERVERS = PFX + "servers"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#userInfo(String, String) * userInfo}. */ public static final String PROP_PASSWORD = PFX + "password"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#userInfo(String, String) * userInfo}. */ public static final String PROP_USERNAME = PFX + "username"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#token(String) token}. */ public static final String PROP_TOKEN = PFX + "token"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#server(String) server}. */ public static final String PROP_URL = PFX + "url"; /** * Property used to configure a builder from a Properties object. {@value}, * see {@link Builder#sslContext(SSLContext) sslContext}. * This property is a boolean flag, but it tells the options parser to use the * default SSL context. Set the default context before creating the options. */ public static final String PROP_SECURE = PFX + "secure"; /** * Property used to configure a builder from a Properties object. * {@value}, see {@link Builder#sslContext(SSLContext) sslContext}. * This property is a boolean flag, but it tells the options parser to use * an SSL context that takes any server TLS certificate and does not provide * its own. The server must have tls_verify turned OFF for this option to work. */ public static final String PROP_OPENTLS = PFX + "opentls"; /** * Property used to configure a builder from a Properties object. * {@value}, see {@link Builder#maxMessagesInOutgoingQueue(int) maxMessagesInOutgoingQueue}. */ public static final String PROP_MAX_MESSAGES_IN_OUTGOING_QUEUE = PFX + "outgoingqueue.maxmessages"; /** * Property used to configure a builder from a Properties object. * {@value}, see {@link Builder#discardMessagesWhenOutgoingQueueFull() * discardMessagesWhenOutgoingQueueFull}. */ public static final String PROP_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL = PFX + "outgoingqueue.discardwhenfull"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#oldRequestStyle() * oldRequestStyle}. */ public static final String PROP_USE_OLD_REQUEST_STYLE = "use.old.request.style"; /** * Property used to configure a builder from a Properties object. {@value}, see {@link Builder#maxControlLine(int) * maxControlLine}. */ public static final String PROP_MAX_CONTROL_LINE = "max.control.line"; /** * Property used to set the inbox prefix */ public static final String PROP_INBOX_PREFIX = "inbox.prefix"; /** * Property used to set whether to ignore discovered servers when connecting */ public static final String PROP_IGNORE_DISCOVERED_SERVERS = "ignore_discovered_servers"; /** * Preferred property used to set whether to ignore discovered servers when connecting */ public static final String PROP_IGNORE_DISCOVERED_SERVERS_PREFERRED = "ignore.discovered.servers"; /** * Property used to set class name for ServerPool implementation * {@link Builder#serverPool(ServerPool) serverPool}. */ public static final String PROP_SERVERS_POOL_IMPLEMENTATION_CLASS = "servers_pool_implementation_class"; /** * Preferred property used to set class name for ServerPool implementation * {@link Builder#serverPool(ServerPool) serverPool}. */ public static final String PROP_SERVERS_POOL_IMPLEMENTATION_CLASS_PREFERRED = "servers.pool.implementation.class"; /** * Property used to set class name for the Dispatcher Factory * {@link Builder#dispatcherFactory(DispatcherFactory) dispatcherFactory}. */ public static final String PROP_DISPATCHER_FACTORY_CLASS = "dispatcher.factory.class"; /** * Property used to set class name for the SSLContextFactory * {@link Builder#sslContextFactory(SSLContextFactory) sslContextFactory}. */ public static final String PROP_SSL_CONTEXT_FACTORY_CLASS = "ssl.context.factory.class"; /** * Property for the keystore path used to create an SSLContext */ public static final String PROP_KEYSTORE = PFX + "keyStore"; /** * Property for the keystore password used to create an SSLContext */ public static final String PROP_KEYSTORE_PASSWORD = PFX + "keyStorePassword"; /** * Property for the truststore path used to create an SSLContext */ public static final String PROP_TRUSTSTORE = PFX + "trustStore"; /** * Property for the truststore password used to create an SSLContext */ public static final String PROP_TRUSTSTORE_PASSWORD = PFX + "trustStorePassword"; /** * Property for the algorithm used to create an SSLContext */ public static final String PROP_TLS_ALGORITHM = PFX + "tls.algorithm"; /** * Property used to set the path to a credentials file to be used in a FileAuthHandler */ public static final String PROP_CREDENTIAL_PATH = PFX + "credential.path"; /** * Property used to configure tls first behavior * This property is a boolean flag, telling connections whether * to do TLS upgrade first, before INFO */ public static final String PROP_TLS_FIRST = PFX + "tls.first"; /** * This property is used to enable support for UTF8 subjects. See {@link Builder#supportUTF8Subjects() supportUTF8Subjects()} */ public static final String PROP_UTF8_SUBJECTS = "allow.utf8.subjects"; /** * Property used to throw {@link java.util.concurrent.TimeoutException} on timeout instead of {@link java.util.concurrent.CancellationException}. * {@link Builder#useTimeoutException()}. */ public static final String PROP_USE_TIMEOUT_EXCEPTION = PFX + "use.timeout.exception"; /** * Property used to a dispatcher that dispatches messages via the executor service instead of with a blocking call. * {@link Builder#useDispatcherWithExecutor()}. */ public static final String PROP_USE_DISPATCHER_WITH_EXECUTOR = PFX + "use.dispatcher.with.executor"; // ---------------------------------------------------------------------------------------------------- // PROTOCOL CONNECT OPTION CONSTANTS // ---------------------------------------------------------------------------------------------------- /** * Protocol key {@value}, see {@link Builder#verbose() verbose}. */ static final String OPTION_VERBOSE = "verbose"; /** * Protocol key {@value}, see {@link Builder#pedantic() * pedantic}. */ static final String OPTION_PEDANTIC = "pedantic"; /** * Protocol key {@value}, see * {@link Builder#sslContext(SSLContext) sslContext}. */ static final String OPTION_TLS_REQUIRED = "tls_required"; /** * Protocol key {@value}, see {@link Builder#token(String) * token}. */ static final String OPTION_AUTH_TOKEN = "auth_token"; /** * Protocol key {@value}, see * {@link Builder#userInfo(String, String) userInfo}. */ static final String OPTION_USER = "user"; /** * Protocol key {@value}, see * {@link Builder#userInfo(String, String) userInfo}. */ static final String OPTION_PASSWORD = "pass"; /** * Protocol key {@value}, see {@link Builder#connectionName(String) * connectionName}. */ static final String OPTION_NAME = "name"; /** * Protocol key {@value}, will be set to "Java". */ static final String OPTION_LANG = "lang"; /** * Protocol key {@value}, will be set to * {@link Nats#CLIENT_VERSION CLIENT_VERSION}. */ static final String OPTION_VERSION = "version"; /** * Protocol key {@value}, will be set to 1. */ static final String OPTION_PROTOCOL = "protocol"; /** * Echo key {@value}, determines if the server should echo to the client. */ static final String OPTION_ECHO = "echo"; /** * NKey key {@value}, the public key being used for sign-in. */ static final String OPTION_NKEY = "nkey"; /** * SIG key {@value}, the signature of the nonce sent by the server. */ static final String OPTION_SIG = "sig"; /** * JWT key {@value}, the user JWT to send to the server. */ static final String OPTION_JWT = "jwt"; /** * Headers key if headers are supported */ static final String OPTION_HEADERS = "headers"; /** * No Responders key if noresponders are supported */ static final String OPTION_NORESPONDERS = "no_responders"; // ---------------------------------------------------------------------------------------------------- // CLASS VARIABLES // ---------------------------------------------------------------------------------------------------- private final List natsServerUris; private final List unprocessedServers; private final boolean noRandomize; private final boolean noResolveHostnames; private final boolean reportNoResponders; private final String connectionName; private final boolean verbose; private final boolean pedantic; private final SSLContext sslContext; private final int maxReconnect; private final int maxControlLine; private final Duration reconnectWait; private final Duration reconnectJitter; private final Duration reconnectJitterTls; private final Duration connectionTimeout; private final int socketReadTimeoutMillis; private final Duration socketWriteTimeout; private final int socketSoLinger; private final Duration pingInterval; private final Duration requestCleanupInterval; private final int maxPingsOut; private final long reconnectBufferSize; private final char[] username; private final char[] password; private final char[] token; private final String inboxPrefix; private boolean useOldRequestStyle; private final int bufferSize; private final boolean noEcho; private final boolean noHeaders; private final boolean noNoResponders; private final boolean clientSideLimitChecks; private final boolean supportUTF8Subjects; private final int maxMessagesInOutgoingQueue; private final boolean discardMessagesWhenOutgoingQueueFull; private final boolean ignoreDiscoveredServers; private final boolean tlsFirst; private final boolean useTimeoutException; private final boolean useDispatcherWithExecutor; private final AuthHandler authHandler; private final ReconnectDelayHandler reconnectDelayHandler; private final ErrorListener errorListener; private final TimeTraceLogger timeTraceLogger; private final ConnectionListener connectionListener; private final StatisticsCollector statisticsCollector; private final String dataPortType; private final boolean trackAdvancedStats; private final boolean traceConnection; private final ExecutorService executor; private final ServerPool serverPool; private final DispatcherFactory dispatcherFactory; private final List> httpRequestInterceptors; private final Proxy proxy; static class DefaultThreadFactory implements ThreadFactory { String name; AtomicInteger threadNo = new AtomicInteger(0); public DefaultThreadFactory (String name){ this.name = name; } public Thread newThread(Runnable r) { String threadName = name+":"+threadNo.incrementAndGet(); Thread t = new Thread(r,threadName); if (t.isDaemon()) { t.setDaemon(false); } if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } } /** * Set old request style. * @param value true to use the old request style * @deprecated Use Builder */ @Deprecated public void setOldRequestStyle(boolean value) { useOldRequestStyle = value; } // ---------------------------------------------------------------------------------------------------- // BUILDER // ---------------------------------------------------------------------------------------------------- /** * Creates a builder for the options in a fluent style * @return the builder. */ public static Builder builder() { return new Builder(); } /** * Options are created using a Builder. The builder supports chaining and will * create a default set of options if no methods are calls. The builder can also * be created from a properties object using the property names defined with the * prefix PROP_ in this class. *

A common usage for testing might be {@code new Options.Builder().server(myserverurl).noReconnect.build()} */ public static class Builder { // ---------------------------------------------------------------------------------------------------- // BUILDER VARIABLES // ---------------------------------------------------------------------------------------------------- private final List natsServerUris = new ArrayList<>(); private final List unprocessedServers = new ArrayList<>(); private boolean noRandomize = false; private boolean noResolveHostnames = false; private boolean reportNoResponders = false; private String connectionName = null; // Useful for debugging -> "test: " + NatsTestServer.currentPort(); private boolean verbose = false; private boolean pedantic = false; private SSLContext sslContext = null; private SSLContextFactory sslContextFactory = null; private int maxControlLine = DEFAULT_MAX_CONTROL_LINE; private int maxReconnect = DEFAULT_MAX_RECONNECT; private Duration reconnectWait = DEFAULT_RECONNECT_WAIT; private Duration reconnectJitter = DEFAULT_RECONNECT_JITTER; private Duration reconnectJitterTls = DEFAULT_RECONNECT_JITTER_TLS; private Duration connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; private int socketReadTimeoutMillis = 0; private Duration socketWriteTimeout = DEFAULT_SOCKET_WRITE_TIMEOUT; private int socketSoLinger = -1; private Duration pingInterval = DEFAULT_PING_INTERVAL; private Duration requestCleanupInterval = DEFAULT_REQUEST_CLEANUP_INTERVAL; private int maxPingsOut = DEFAULT_MAX_PINGS_OUT; private long reconnectBufferSize = DEFAULT_RECONNECT_BUF_SIZE; private char[] username = null; private char[] password = null; private char[] token = null; private boolean useOldRequestStyle = false; private int bufferSize = DEFAULT_BUFFER_SIZE; private boolean trackAdvancedStats = false; private boolean traceConnection = false; private boolean noEcho = false; private boolean noHeaders = false; private boolean noNoResponders = false; private boolean clientSideLimitChecks = true; private boolean supportUTF8Subjects = false; private String inboxPrefix = DEFAULT_INBOX_PREFIX; private int maxMessagesInOutgoingQueue = DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE; private boolean discardMessagesWhenOutgoingQueueFull = DEFAULT_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL; private boolean ignoreDiscoveredServers = false; private boolean tlsFirst = false; private boolean useTimeoutException = false; private boolean useDispatcherWithExecutor = false; private ServerPool serverPool = null; private DispatcherFactory dispatcherFactory = null; private AuthHandler authHandler; private ReconnectDelayHandler reconnectDelayHandler; private ErrorListener errorListener = null; private TimeTraceLogger timeTraceLogger = null; private ConnectionListener connectionListener = null; private StatisticsCollector statisticsCollector = null; private String dataPortType = DEFAULT_DATA_PORT_TYPE; private ExecutorService executor; private List> httpRequestInterceptors; private Proxy proxy; private boolean useDefaultTls; private boolean useTrustAllTls; private String keystore; private char[] keystorePassword; private String truststore; private char[] truststorePassword; private String tlsAlgorithm = DEFAULT_TLS_ALGORITHM; private String credentialPath; /** * Constructs a new Builder with the default values. *

When {@link #build() build()} is called on a default builder it will add the {@link Options#DEFAULT_URL * default url} to its list of servers if there were no servers defined.

*/ public Builder() {} // ---------------------------------------------------------------------------------------------------- // BUILD CONSTRUCTOR PROPS // ---------------------------------------------------------------------------------------------------- /** * Constructs a new {@code Builder} from a {@link Properties} object. *

Methods called on the builder after construction can override the properties.

* @param props the {@link Properties} object */ public Builder(Properties props) throws IllegalArgumentException { properties(props); } /** * Constructs a new {@code Builder} from a file that contains properties. * @param propertiesFilePath a resolvable path to a file from the location the application is running, either relative or absolute * @throws IOException if the properties file cannot be found, opened or read */ public Builder(String propertiesFilePath) throws IOException { Properties props = new Properties(); props.load(Files.newInputStream(Paths.get(propertiesFilePath))); properties(props); } // ---------------------------------------------------------------------------------------------------- // BUILDER METHODS // ---------------------------------------------------------------------------------------------------- /** * Add settings defined in the properties object * @param props the properties object * @throws IllegalArgumentException if the properties object is null * @return the Builder for chaining */ public Builder properties(Properties props) { if (props == null) { throw new IllegalArgumentException("Properties cannot be null"); } stringProperty(props, PROP_URL, this::server); stringProperty(props, PROP_SERVERS, str -> { String[] servers = str.trim().split(",\\s*"); this.servers(servers); }); charArrayProperty(props, PROP_USERNAME, ca -> this.username = ca); charArrayProperty(props, PROP_PASSWORD, ca -> this.password = ca); charArrayProperty(props, PROP_TOKEN, ca -> this.token = ca); booleanProperty(props, PROP_SECURE, b -> this.useDefaultTls = b); booleanProperty(props, PROP_OPENTLS, b -> this.useTrustAllTls = b); classnameProperty(props, PROP_SSL_CONTEXT_FACTORY_CLASS, o -> this.sslContextFactory = (SSLContextFactory) o); stringProperty(props, PROP_KEYSTORE, s -> this.keystore = s); charArrayProperty(props, PROP_KEYSTORE_PASSWORD, ca -> this.keystorePassword = ca); stringProperty(props, PROP_TRUSTSTORE, s -> this.truststore = s); charArrayProperty(props, PROP_TRUSTSTORE_PASSWORD, ca -> this.truststorePassword = ca); stringProperty(props, PROP_TLS_ALGORITHM, s -> this.tlsAlgorithm = s); stringProperty(props, PROP_CREDENTIAL_PATH, s -> this.credentialPath = s); stringProperty(props, PROP_CONNECTION_NAME, s -> this.connectionName = s); booleanProperty(props, PROP_NORANDOMIZE, b -> this.noRandomize = b); booleanProperty(props, PROP_NO_RESOLVE_HOSTNAMES, b -> this.noResolveHostnames = b); booleanProperty(props, PROP_REPORT_NO_RESPONDERS, b -> this.reportNoResponders = b); stringProperty(props, PROP_CONNECTION_NAME, s -> this.connectionName = s); booleanProperty(props, PROP_VERBOSE, b -> this.verbose = b); booleanProperty(props, PROP_NO_ECHO, b -> this.noEcho = b); booleanProperty(props, PROP_NO_HEADERS, b -> this.noHeaders = b); booleanProperty(props, PROP_NO_NORESPONDERS, b -> this.noNoResponders = b); booleanProperty(props, PROP_CLIENT_SIDE_LIMIT_CHECKS, b -> this.clientSideLimitChecks = b); booleanProperty(props, PROP_UTF8_SUBJECTS, b -> this.supportUTF8Subjects = b); booleanProperty(props, PROP_PEDANTIC, b -> this.pedantic = b); intProperty(props, PROP_MAX_RECONNECT, DEFAULT_MAX_RECONNECT, i -> this.maxReconnect = i); durationProperty(props, PROP_RECONNECT_WAIT, DEFAULT_RECONNECT_WAIT, d -> this.reconnectWait = d); durationProperty(props, PROP_RECONNECT_JITTER, DEFAULT_RECONNECT_JITTER, d -> this.reconnectJitter = d); durationProperty(props, PROP_RECONNECT_JITTER_TLS, DEFAULT_RECONNECT_JITTER_TLS, d -> this.reconnectJitterTls = d); longProperty(props, PROP_RECONNECT_BUF_SIZE, DEFAULT_RECONNECT_BUF_SIZE, l -> this.reconnectBufferSize = l); durationProperty(props, PROP_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, d -> this.connectionTimeout = d); intProperty(props, PROP_SOCKET_READ_TIMEOUT_MS, -1, i -> this.socketReadTimeoutMillis = i); durationProperty(props, PROP_SOCKET_WRITE_TIMEOUT, DEFAULT_SOCKET_WRITE_TIMEOUT, d -> this.socketWriteTimeout = d); intProperty(props, PROP_SOCKET_SO_LINGER, -1, i -> socketSoLinger = i); intGtEqZeroProperty(props, PROP_MAX_CONTROL_LINE, DEFAULT_MAX_CONTROL_LINE, i -> this.maxControlLine = i); durationProperty(props, PROP_PING_INTERVAL, DEFAULT_PING_INTERVAL, d -> this.pingInterval = d); durationProperty(props, PROP_CLEANUP_INTERVAL, DEFAULT_REQUEST_CLEANUP_INTERVAL, d -> this.requestCleanupInterval = d); intProperty(props, PROP_MAX_PINGS, DEFAULT_MAX_PINGS_OUT, i -> this.maxPingsOut = i); booleanProperty(props, PROP_USE_OLD_REQUEST_STYLE, b -> this.useOldRequestStyle = b); classnameProperty(props, PROP_ERROR_LISTENER, o -> this.errorListener = (ErrorListener) o); classnameProperty(props, PROP_TIME_TRACE_LOGGER, o -> this.timeTraceLogger = (TimeTraceLogger) o); classnameProperty(props, PROP_CONNECTION_CB, o -> this.connectionListener = (ConnectionListener) o); classnameProperty(props, PROP_STATISTICS_COLLECTOR, o -> this.statisticsCollector = (StatisticsCollector) o); stringProperty(props, PROP_DATA_PORT_TYPE, s -> this.dataPortType = s); stringProperty(props, PROP_INBOX_PREFIX, this::inboxPrefix); intGtEqZeroProperty(props, PROP_MAX_MESSAGES_IN_OUTGOING_QUEUE, DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE, i -> this.maxMessagesInOutgoingQueue = i); booleanProperty(props, PROP_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL, b -> this.discardMessagesWhenOutgoingQueueFull = b); booleanProperty(props, PROP_IGNORE_DISCOVERED_SERVERS, b -> this.ignoreDiscoveredServers = b); booleanProperty(props, PROP_TLS_FIRST, b -> this.tlsFirst = b); booleanProperty(props, PROP_USE_TIMEOUT_EXCEPTION, b -> this.useTimeoutException = b); booleanProperty(props, PROP_USE_DISPATCHER_WITH_EXECUTOR, b -> this.useDispatcherWithExecutor = b); classnameProperty(props, PROP_SERVERS_POOL_IMPLEMENTATION_CLASS, o -> this.serverPool = (ServerPool) o); classnameProperty(props, PROP_DISPATCHER_FACTORY_CLASS, o -> this.dispatcherFactory = (DispatcherFactory) o); return this; } /** * Add a server to the list of known servers. * * @param serverURL the URL for the server to add * @throws IllegalArgumentException if the url is not formatted correctly. * @return the Builder for chaining */ public Builder server(String serverURL) { return servers(serverURL.trim().split(",")); } /** * Add an array of servers to the list of known servers. * * @param servers A list of server URIs * @throws IllegalArgumentException if any url is not formatted correctly. * @return the Builder for chaining */ public Builder servers(String[] servers) { for (String s : servers) { if (s != null && !s.isEmpty()) { try { String unprocessed = s.trim(); NatsUri nuri = new NatsUri(unprocessed); if (!natsServerUris.contains(nuri)) { natsServerUris.add(nuri); unprocessedServers.add(unprocessed); } } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } } return this; } /** * Turn on the old request style that uses a new inbox and subscriber for each * request. * @return the Builder for chaining */ public Builder oldRequestStyle() { this.useOldRequestStyle = true; return this; } /** * For the default server list provider, turn off server pool randomization. * The default provider will pick servers from its list randomly on a reconnect. * When noRandomize is set to true the default provider supplies a list that * first contains servers as configured and then contains the servers as sent * from the connected server. * @return the Builder for chaining */ public Builder noRandomize() { this.noRandomize = true; return this; } /** * For the default server list provider, whether to resolve hostnames when building server list. * @return the Builder for chaining */ public Builder noResolveHostnames() { this.noResolveHostnames = true; return this; } public Builder reportNoResponders() { this.reportNoResponders = true; return this; } /** * Turn off echo. If supported by the nats-server version you are connecting to this * flag will prevent the server from echoing messages back to the connection if it * has subscriptions on the subject being published to. * @return the Builder for chaining */ public Builder noEcho() { this.noEcho = true; return this; } /** * Turn off header support. Some versions of the server don't support it. * It's also not required if you don't use headers * @return the Builder for chaining */ public Builder noHeaders() { this.noHeaders = true; return this; } /** * Turn off noresponder support. Some versions of the server don't support it. * @return the Builder for chaining */ public Builder noNoResponders() { this.noNoResponders = true; return this; } /** * Set client side limit checks. Default is true * @param checks the checks flag * @return the Builder for chaining */ public Builder clientSideLimitChecks(boolean checks) { this.clientSideLimitChecks = checks; return this; } /** * The client protocol is not clear about the encoding for subject names. For * performance reasons, the Java client defaults to ASCII. You can enable UTF8 * with this method. The server, written in go, treats byte to string as UTF8 by default * and should allow UTF8 subjects, but make sure to test any clients when using them. * @return the Builder for chaining */ public Builder supportUTF8Subjects() { this.supportUTF8Subjects = true; return this; } /** * Set the connection's optional Name. * * @param name the connections new name. * @return the Builder for chaining */ public Builder connectionName(String name) { this.connectionName = name; return this; } /** * Set the connection's inbox prefix. All inboxes will start with this string. * * @param prefix prefix to use. * @return the Builder for chaining */ public Builder inboxPrefix(String prefix) { this.inboxPrefix = prefix; if (!this.inboxPrefix.endsWith(".")) { this.inboxPrefix = this.inboxPrefix + "."; } return this; } /** * Turn on verbose mode with the server. * @return the Builder for chaining */ public Builder verbose() { this.verbose = true; return this; } /** * Turn on pedantic mode for the server, in relation to this connection. * @return the Builder for chaining */ public Builder pedantic() { this.pedantic = true; return this; } /** * Turn on advanced stats, primarily for test/benchmarks. These are visible if you * call toString on the {@link Statistics Statistics} object. * @return the Builder for chaining */ public Builder turnOnAdvancedStats() { this.trackAdvancedStats = true; return this; } /** * Enable connection trace messages. Messages are printed to standard out. This option is for very * fine-grained debugging of connection issues. * @return the Builder for chaining */ public Builder traceConnection() { this.traceConnection = true; return this; } /** * Sets the options to use the default SSL Context, if it exists. * @throws NoSuchAlgorithmException Not thrown, deferred to build() method, left in for backward compatibility * @return the Builder for chaining */ public Builder secure() throws NoSuchAlgorithmException { useDefaultTls = true; return this; } /** * Set the options to use an SSL context that accepts any server certificate and has no client certificates. * @throws NoSuchAlgorithmException Not thrown, deferred to build() method, left in for backward compatibility * @return the Builder for chaining */ public Builder opentls() throws NoSuchAlgorithmException { useTrustAllTls = true; return this; } /** * Set the SSL context, requires that the server supports TLS connections and * the URI specifies TLS. * If provided, the context takes precedence over any other TLS/SSL properties * set in the builder, including the sslContextFactory * @param ctx the SSL Context to use for TLS connections * @return the Builder for chaining */ public Builder sslContext(SSLContext ctx) { this.sslContext = ctx; return this; } /** * Set the factory that provides the ssl context. The factory is superseded * by an instance of SSLContext * @param sslContextFactory the SSL Context for use to create a ssl context * @return the Builder for chaining */ public Builder sslContextFactory(SSLContextFactory sslContextFactory) { this.sslContextFactory = sslContextFactory; return this; } /** * * @param keystore the path to the keystore file * @return the Builder for chaining */ public Builder keystorePath(String keystore) { this.keystore = emptyAsNull(keystore); return this; } /** * * @param keystorePassword the password for the keystore * @return the Builder for chaining */ public Builder keystorePassword(char[] keystorePassword) { this.keystorePassword = keystorePassword == null || keystorePassword.length == 0 ? null : keystorePassword; return this; } /** * * @param truststore the path to the trust store file * @return the Builder for chaining */ public Builder truststorePath(String truststore) { this.truststore = emptyAsNull(truststore); return this; } /** * * @param truststorePassword the password for the trust store * @return the Builder for chaining */ public Builder truststorePassword(char[] truststorePassword) { this.truststorePassword = truststorePassword == null || truststorePassword.length == 0 ? null : truststorePassword; return this; } /** * * @param tlsAlgorithm the tls algorithm. Default is {@value SSLUtils#DEFAULT_TLS_ALGORITHM} * @return the Builder for chaining */ public Builder tlsAlgorithm(String tlsAlgorithm) { this.tlsAlgorithm = emptyOrNullAs(tlsAlgorithm, DEFAULT_TLS_ALGORITHM); return this; } /** * * @param credentialPath the path to the credentials file for creating an {@link AuthHandler AuthHandler} * @return the Builder for chaining */ public Builder credentialPath(String credentialPath) { this.credentialPath = emptyAsNull(credentialPath); return this; } /** * Equivalent to calling maxReconnects with 0, {@link #maxReconnects(int) maxReconnects}. * @return the Builder for chaining */ public Builder noReconnect() { this.maxReconnect = 0; return this; } /** * Set the maximum number of reconnect attempts. Use 0 to turn off * auto-reconnect. Use -1 to turn on infinite reconnects. * *

The reconnect count is incremented on a per-server basis, so if the server list contains 5 servers * but max reconnects is set to 3, only 3 of those servers will be tried.

* *

This library has a slight difference from some NATS clients, if you set the maxReconnects to zero * there will not be any reconnect attempts, regardless of the number of known servers.

* *

The reconnect state is entered when the connection is connected and loses * that connection. During the initial connection attempt, the client will cycle over * its server list one time, regardless of what maxReconnects is set to. The only exception * to this is the experimental async connect method {@link Nats#connectAsynchronously(Options, boolean) connectAsynchronously}.

* * @param max the maximum reconnect attempts * @return the Builder for chaining */ public Builder maxReconnects(int max) { this.maxReconnect = max; return this; } /** * Set the time to wait between reconnect attempts to the same server. This setting is only used * by the client when the same server appears twice in the reconnect attempts, either because it is the * only known server or by random chance. Note, the randomization of the server list doesn't occur per * attempt, it is performed once at the start, so if there are 2 servers in the list you will never encounter * the reconnect wait. * * @param time the time to wait * @return the Builder for chaining */ public Builder reconnectWait(Duration time) { this.reconnectWait = time; return this; } /** * Set the jitter time to wait between reconnect attempts to the same server. This setting is used to vary * the reconnect wait to avoid multiple clients trying to reconnect to servers at the same time. * * @param time the time to wait * @return the Builder for chaining */ public Builder reconnectJitter(Duration time) { this.reconnectJitter = time; return this; } /** * Set the jitter time for a tls/secure connection to wait between reconnect attempts to the same server. * This setting is used to vary the reconnect wait to avoid multiple clients trying to reconnect to * servers at the same time. * * @param time the time to wait * @return the Builder for chaining */ public Builder reconnectJitterTls(Duration time) { this.reconnectJitterTls = time; return this; } /** * Set the maximum length of a control line sent by this connection. This value is also configured * in the server but the protocol doesn't currently forward that setting. Configure it here so that * the client can ensure that messages are valid before sending to the server. * * @param bytes the max byte count * @return the Builder for chaining */ public Builder maxControlLine(int bytes) { this.maxControlLine = bytes < 0 ? DEFAULT_MAX_CONTROL_LINE : bytes; return this; } /** * Set the timeout for connection attempts. Each server in the options is allowed this timeout * so if 3 servers are tried with a timeout of 5s the total time could be 15s. * * @param connectionTimeout the time to wait * @return the Builder for chaining */ public Builder connectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; return this; } /** * Set the timeout for connection attempts. Each server in the options is allowed this timeout * so if 3 servers are tried with a timeout of 5s the total time could be 15s. * * @param connectionTimeoutMillis the time to wait in milliseconds * @return the Builder for chaining */ public Builder connectionTimeout(long connectionTimeoutMillis) { this.connectionTimeout = Duration.ofMillis(connectionTimeoutMillis); return this; } /** * Set the timeout to use around socket reads * @param socketReadTimeoutMillis the timeout milliseconds * @return the Builder for chaining */ public Builder socketReadTimeoutMillis(int socketReadTimeoutMillis) { this.socketReadTimeoutMillis = socketReadTimeoutMillis; return this; } /** * Set the timeout to use around socket writes * @param socketWriteTimeoutMillis the timeout milliseconds * @return the Builder for chaining */ public Builder socketWriteTimeout(long socketWriteTimeoutMillis) { socketWriteTimeout = Duration.ofMillis(socketWriteTimeoutMillis); return this; } /** * Set the timeout to use around socket writes * @param socketWriteTimeout the timeout duration * @return the Builder for chaining */ public Builder socketWriteTimeout(Duration socketWriteTimeout) { this.socketWriteTimeout = socketWriteTimeout; return this; } /** * Set the value of the socket SO LINGER property in seconds. * This feature is used by library data port implementations. * Setting this is a last resort if socket closes are a problem * in your environment, otherwise it's generally not necessary * to set this. The value must be greater than or equal to 0 * to have the code call socket.setSoLinger with true and the timeout value * @param socketSoLinger the number of seconds to linger * @return the Builder for chaining */ public Builder socketSoLinger(int socketSoLinger) { this.socketSoLinger = socketSoLinger; return this; } /** * Set the interval between attempts to pings the server. These pings are automated, * and capped by {@link #maxPingsOut(int) maxPingsOut()}. As of 2.4.4 the library * may wait up to 2 * time to send a ping. Incoming traffic from the server can postpone * the next ping to avoid pings taking up bandwidth during busy messaging. * Keep in mind that a ping requires a round trip to the server. Setting this value to a small * number can result in quick failures due to maxPingsOut being reached, these failures will * force a disconnect/reconnect which can result in messages being held back or failed. In general, * the ping interval should be set in seconds but this value is not enforced as it would result in * an API change from the 2.0 release. * * @param time the time between client to server pings * @return the Builder for chaining */ public Builder pingInterval(Duration time) { this.pingInterval = time == null ? DEFAULT_PING_INTERVAL : time; return this; } /** * Set the interval between cleaning passes on outstanding request futures that are cancelled or timeout * in the application code. * *

The default value is probably reasonable, but this interval is useful in a very noisy network * situation where lots of requests are used. * * @param time the cleaning interval * @return the Builder for chaining */ public Builder requestCleanupInterval(Duration time) { this.requestCleanupInterval = time; return this; } /** * Set the maximum number of pings the client can have in flight. * * @param max the max pings * @return the Builder for chaining */ public Builder maxPingsOut(int max) { this.maxPingsOut = max; return this; } /** * Sets the initial size for buffers in the connection, primarily for testing. * @param size the size in bytes to make buffers for connections created with this options * @return the Builder for chaining */ public Builder bufferSize(int size) { this.bufferSize = size; return this; } /** * Set the maximum number of bytes to buffer in the client when trying to * reconnect. When this value is exceeded the client will start to drop messages. * The count of dropped messages can be read from the {@link Statistics#getDroppedCount() Statistics}. * A value of zero will disable the reconnect buffer, a value less than zero means unlimited. Caution * should be used for negative numbers as they can result in an unreliable network connection plus a * high message rate leading to an out of memory error. * * @param size the size in bytes * @return the Builder for chaining */ public Builder reconnectBufferSize(long size) { this.reconnectBufferSize = size; return this; } /** * Set the username and password for basic authentication. * If the user and password are set in the server URL, they will override these values. However, in a clustering situation, * these values can be used as a fallback. * use the char[] version instead for better security * * @param userName a non-empty userName * @param password the password, in plain text * @return the Builder for chaining */ public Builder userInfo(String userName, String password) { this.username = userName.toCharArray(); this.password = password.toCharArray(); return this; } /** * Set the username and password for basic authentication. * If the user and password are set in the server URL, they will override these values. However, in a clustering situation, * these values can be used as a fallback. * * @param userName a non-empty userName * @param password the password, in plain text * @return the Builder for chaining */ public Builder userInfo(char[] userName, char[] password) { this.username = userName; this.password = password; return this; } /** * Set the token for token-based authentication. * If a token is provided in a server URI it overrides this value. * * @param token The token * @return the Builder for chaining * @deprecated use the char[] version instead for better security */ @Deprecated public Builder token(String token) { this.token = token.toCharArray(); return this; } /** * Set the token for token-based authentication. * If a token is provided in a server URI it overrides this value. * * @param token The token * @return the Builder for chaining */ public Builder token(char[] token) { this.token = token; return this; } /** * Set the {@link AuthHandler AuthHandler} to sign the server nonce for authentication in * nonce-mode. * * @param handler The new AuthHandler for this connection. * @return the Builder for chaining */ public Builder authHandler(AuthHandler handler) { this.authHandler = handler; return this; } /** * Set the {@link ReconnectDelayHandler ReconnectDelayHandler} for custom reconnect duration * * @param handler The new ReconnectDelayHandler for this connection. * @return the Builder for chaining */ public Builder reconnectDelayHandler(ReconnectDelayHandler handler) { this.reconnectDelayHandler = handler; return this; } /** * Set the {@link ErrorListener ErrorListener} to receive asynchronous error events related to this * connection. * * @param listener The new ErrorListener for this connection. * @return the Builder for chaining */ public Builder errorListener(ErrorListener listener) { this.errorListener = listener; return this; } /** * Set the {@link TimeTraceLogger TimeTraceLogger} to receive trace events related to this connection. * @param logger The new TimeTraceLogger for this connection. * @return the Builder for chaining */ public Builder timeTraceLogger(TimeTraceLogger logger) { this.timeTraceLogger = logger; return this; } /** * Set the {@link ConnectionListener ConnectionListener} to receive asynchronous notifications of disconnect * events. * * @param listener The new ConnectionListener for this type of event. * @return the Builder for chaining */ public Builder connectionListener(ConnectionListener listener) { this.connectionListener = listener; return this; } /** * Set the {@link StatisticsCollector StatisticsCollector} to collect connection metrics. *

* If not set, then a default implementation will be used. * * @param collector the new StatisticsCollector for this connection. * @return the Builder for chaining */ public Builder statisticsCollector(StatisticsCollector collector) { this.statisticsCollector = collector; return this; } /** * Set the {@link ExecutorService ExecutorService} used to run threaded tasks. The default is a * cached thread pool that names threads after the connection name (or a default). This executor * is used for reading and writing the underlying sockets as well as for each Dispatcher. * The default executor uses a short keepalive time, 500ms, to insure quick shutdowns. This is reasonable * since most threads from the executor are long-lived. If you customize, be sure to keep the shutdown * effect in mind, executors can block for their keepalive time. The default executor also marks threads * with priority normal and as non-daemon. * * @param executor The ExecutorService to use for connections built with these options. * @return the Builder for chaining */ public Builder executor(ExecutorService executor) { this.executor = executor; return this; } /** * Add an HttpRequest interceptor which can be used to modify the HTTP request when using websockets * * @param interceptor The interceptor * @return the Builder for chaining */ public Builder httpRequestInterceptor(java.util.function.Consumer interceptor) { if (null == this.httpRequestInterceptors) { this.httpRequestInterceptors = new ArrayList<>(); } this.httpRequestInterceptors.add(interceptor); return this; } /** * Overwrite the list of HttpRequest interceptors which can be used to modify the HTTP request when using websockets * * @param interceptors The list of interceptors * @return the Builder for chaining */ public Builder httpRequestInterceptors(Collection> interceptors) { this.httpRequestInterceptors = new ArrayList<>(interceptors); return this; } /** * Define a proxy to use when connecting. * * @param proxy is the HTTP or socks proxy to use. * @return the Builder for chaining */ public Builder proxy(Proxy proxy) { this.proxy = proxy; return this; } /** * The class to use for this connections data port. This is an advanced setting * and primarily useful for testing. * * @param dataPortClassName a valid and accessible class name * @return the Builder for chaining */ public Builder dataPortType(String dataPortClassName) { this.dataPortType = dataPortClassName == null ? DEFAULT_DATA_PORT_TYPE : dataPortClassName; return this; } /** * Set the maximum number of messages in the outgoing queue. * * @param maxMessagesInOutgoingQueue the maximum number of messages in the outgoing queue * @return the Builder for chaining */ public Builder maxMessagesInOutgoingQueue(int maxMessagesInOutgoingQueue) { this.maxMessagesInOutgoingQueue = maxMessagesInOutgoingQueue < 0 ? DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE : maxMessagesInOutgoingQueue; return this; } /** * Enable discard messages when the outgoing queue full. See {@link Builder#maxMessagesInOutgoingQueue(int) maxMessagesInOutgoingQueue} * * @return the Builder for chaining */ public Builder discardMessagesWhenOutgoingQueueFull() { this.discardMessagesWhenOutgoingQueueFull = true; return this; } /** * Turn off use of discovered servers when connecting / reconnecting. Used in the default server list provider. * @return the Builder for chaining */ public Builder ignoreDiscoveredServers() { this.ignoreDiscoveredServers = true; return this; } /** * Set TLS Handshake First behavior on. Default is off. * TLS Handshake First is used to instruct the library perform * the TLS handshake right after the connect and before receiving * the INFO protocol from the server. If this option is enabled * but the server is not configured to perform the TLS handshake * first, the connection will fail. * @return the Builder for chaining */ public Builder tlsFirst() { this.tlsFirst = true; return this; } /** * Throw {@link java.util.concurrent.TimeoutException} on timeout instead of {@link java.util.concurrent.CancellationException}? * @return the Builder for chaining */ public Builder useTimeoutException() { this.useTimeoutException = true; return this; } /** * Instruct dispatchers to dispatch all messages as a task, instead of directly from dispatcher thread * @return the Builder for chaining */ public Builder useDispatcherWithExecutor() { this.useDispatcherWithExecutor = true; return this; } /** * Set the ServerPool implementation for connections to use instead of the default implementation * @param serverPool the implementation * @return the Builder for chaining */ public Builder serverPool(ServerPool serverPool) { this.serverPool = serverPool; return this; } /** * Set the DispatcherFactory implementation for connections to use instead of the default implementation * @param dispatcherFactory the implementation * @return the Builder for chaining */ public Builder dispatcherFactory(DispatcherFactory dispatcherFactory) { this.dispatcherFactory = dispatcherFactory; return this; } /** * Build an Options object from this Builder. * *

If the Options builder was not provided with a server, a default one will be included * {@link Options#DEFAULT_URL}. If only a single server URI is included, the builder * will try a few things to make connecting easier: *

    *
  • If there is no user/password is set but the URI has them, {@code nats://user:password@server:port}, they will be used. *
  • If there is no token is set but the URI has one, {@code nats://token@server:port}, it will be used. *
  • If the URI is of the form tls:// and no SSL context was assigned, one is created, see {@link Options.Builder#secure() secure()}. *
  • If the URI is of the form opentls:// and no SSL context was assigned one will be created * that does not check the servers certificate for validity. This is not secure and only provided * for tests and development. *
* * @return the new options object * @throws IllegalStateException if there is a conflict in the options, like a token and a user/pass */ public Options build() throws IllegalStateException { // ---------------------------------------------------------------------------------------------------- // BUILD IMPL // ---------------------------------------------------------------------------------------------------- if (this.username != null && this.token != null) { throw new IllegalStateException("Options can't have token and username"); } if (inboxPrefix == null) { inboxPrefix = DEFAULT_INBOX_PREFIX; } boolean checkUrisForSecure = true; if (natsServerUris.isEmpty()) { server(DEFAULT_URL); checkUrisForSecure = false; } // ssl context can be directly provided, but if it's not // there might be a factory, or just see if we should make it ourselves if (sslContext == null) { if (sslContextFactory != null) { sslContext = sslContextFactory.createSSLContext(new SSLContextFactoryProperties.Builder() .keystore(keystore) .keystorePassword(keystorePassword) .truststore(truststore) .truststorePassword(truststorePassword) .tlsAlgorithm(tlsAlgorithm) .build()); } else { if (keystore != null || truststore != null) { // the user provided keystore/truststore properties, the want us to make the sslContext that way try { sslContext = SSLUtils.createSSLContext(keystore, keystorePassword, truststore, truststorePassword, tlsAlgorithm); } catch (Exception e) { throw new IllegalStateException("Unable to create SSL context", e); } } else { // the sslContext has not been requested via factory or keystore/truststore properties // If we haven't been told to use the default or the trust all context // and the server isn't the default url, check to see if the server uris // suggest we need the ssl context. if (!useDefaultTls && !useTrustAllTls && checkUrisForSecure) { for (int i = 0; sslContext == null && i < natsServerUris.size(); i++) { NatsUri natsUri = natsServerUris.get(i); switch (natsUri.getScheme()) { case TLS_PROTOCOL: case SECURE_WEBSOCKET_PROTOCOL: useDefaultTls = true; break; case OPENTLS_PROTOCOL: useTrustAllTls = true; break; } } } // check trust all (open) first, in case they provided both // PROP_SECURE (secure) and PROP_OPENTLS (opentls) if (useTrustAllTls) { try { this.sslContext = SSLUtils.createTrustAllTlsContext(); } catch (GeneralSecurityException e) { throw new IllegalStateException("Unable to create SSL context", e); } } else if (useDefaultTls) { try { this.sslContext = SSLContext.getDefault(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Unable to create default SSL context", e); } } } } } if (tlsFirst && sslContext == null) { throw new IllegalStateException("SSL context required for tls handshake first"); } if (credentialPath != null) { File file = new File(credentialPath).getAbsoluteFile(); authHandler = Nats.credentials(file.toString()); } if (this.executor == null) { String threadPrefix = nullOrEmpty(this.connectionName) ? DEFAULT_THREAD_NAME_PREFIX : this.connectionName; this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 500L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), new DefaultThreadFactory(threadPrefix)); } if (socketReadTimeoutMillis > 0) { long srtMin = pingInterval.toMillis() + MINIMUM_SOCKET_WRITE_TIMEOUT_GT_CONNECTION_TIMEOUT; if (socketReadTimeoutMillis < srtMin) { throw new IllegalStateException("Socket Read Timeout must be at least " + MINIMUM_SOCKET_READ_TIMEOUT_GT_CONNECTION_TIMEOUT + " milliseconds greater than the Ping Interval"); } } if (socketWriteTimeout == null || socketWriteTimeout.toMillis() < 1) { socketWriteTimeout = null; } else { long swtMin = connectionTimeout.toMillis() + MINIMUM_SOCKET_WRITE_TIMEOUT_GT_CONNECTION_TIMEOUT; if (socketWriteTimeout.toMillis() < swtMin) { throw new IllegalStateException("Socket Write Timeout must be at least " + MINIMUM_SOCKET_WRITE_TIMEOUT_GT_CONNECTION_TIMEOUT + " milliseconds greater than the Connection Timeout"); } } if (socketSoLinger < 0) { socketSoLinger = -1; } if (errorListener == null) { errorListener = new ErrorListenerLoggerImpl(); } if (timeTraceLogger == null) { if (traceConnection) { timeTraceLogger = (format, args) -> { String timeStr = DateTimeFormatter.ISO_TIME.format(LocalDateTime.now()); System.out.println("[" + timeStr + "] connect trace: " + String.format(format, args)); }; } else { timeTraceLogger = (f, a) -> {}; } } else { // if the dev provided an impl, we assume they meant to time trace the connection traceConnection = true; } return new Options(this); } // ---------------------------------------------------------------------------------------------------- // BUILDER COPY CONSTRUCTOR // ---------------------------------------------------------------------------------------------------- public Builder(Options o) { if (o == null) { throw new IllegalArgumentException("Options cannot be null"); } this.natsServerUris.addAll(o.natsServerUris); this.unprocessedServers.addAll(o.unprocessedServers); this.noRandomize = o.noRandomize; this.noResolveHostnames = o.noResolveHostnames; this.reportNoResponders = o.reportNoResponders; this.connectionName = o.connectionName; this.verbose = o.verbose; this.pedantic = o.pedantic; this.sslContext = o.sslContext; this.maxReconnect = o.maxReconnect; this.reconnectWait = o.reconnectWait; this.reconnectJitter = o.reconnectJitter; this.reconnectJitterTls = o.reconnectJitterTls; this.connectionTimeout = o.connectionTimeout; this.socketReadTimeoutMillis = o.socketReadTimeoutMillis; this.socketWriteTimeout = o.socketWriteTimeout; this.socketSoLinger = o.socketSoLinger; this.pingInterval = o.pingInterval; this.requestCleanupInterval = o.requestCleanupInterval; this.maxPingsOut = o.maxPingsOut; this.reconnectBufferSize = o.reconnectBufferSize; this.username = o.username; this.password = o.password; this.token = o.token; this.useOldRequestStyle = o.useOldRequestStyle; this.maxControlLine = o.maxControlLine; this.bufferSize = o.bufferSize; this.noEcho = o.noEcho; this.noHeaders = o.noHeaders; this.noNoResponders = o.noNoResponders; this.clientSideLimitChecks = o.clientSideLimitChecks; this.supportUTF8Subjects = o.supportUTF8Subjects; this.inboxPrefix = o.inboxPrefix; this.traceConnection = o.traceConnection; this.maxMessagesInOutgoingQueue = o.maxMessagesInOutgoingQueue; this.discardMessagesWhenOutgoingQueueFull = o.discardMessagesWhenOutgoingQueueFull; this.authHandler = o.authHandler; this.reconnectDelayHandler = o.reconnectDelayHandler; this.errorListener = o.errorListener; this.timeTraceLogger = o.timeTraceLogger; this.connectionListener = o.connectionListener; this.statisticsCollector = o.statisticsCollector; this.dataPortType = o.dataPortType; this.trackAdvancedStats = o.trackAdvancedStats; this.executor = o.executor; this.httpRequestInterceptors = o.httpRequestInterceptors; this.proxy = o.proxy; this.ignoreDiscoveredServers = o.ignoreDiscoveredServers; this.tlsFirst = o.tlsFirst; this.useTimeoutException = o.useTimeoutException; this.useDispatcherWithExecutor = o.useDispatcherWithExecutor; this.serverPool = o.serverPool; this.dispatcherFactory = o.dispatcherFactory; } } // ---------------------------------------------------------------------------------------------------- // CONSTRUCTOR // ---------------------------------------------------------------------------------------------------- private Options(Builder b) { this.natsServerUris = Collections.unmodifiableList(b.natsServerUris); this.unprocessedServers = Collections.unmodifiableList(b.unprocessedServers); // exactly how the user gave them this.noRandomize = b.noRandomize; this.noResolveHostnames = b.noResolveHostnames; this.reportNoResponders = b.reportNoResponders; this.connectionName = b.connectionName; this.verbose = b.verbose; this.pedantic = b.pedantic; this.sslContext = b.sslContext; this.maxReconnect = b.maxReconnect; this.reconnectWait = b.reconnectWait; this.reconnectJitter = b.reconnectJitter; this.reconnectJitterTls = b.reconnectJitterTls; this.connectionTimeout = b.connectionTimeout; this.socketReadTimeoutMillis = b.socketReadTimeoutMillis; this.socketWriteTimeout = b.socketWriteTimeout; this.socketSoLinger = b.socketSoLinger; this.pingInterval = b.pingInterval; this.requestCleanupInterval = b.requestCleanupInterval; this.maxPingsOut = b.maxPingsOut; this.reconnectBufferSize = b.reconnectBufferSize; this.username = b.username; this.password = b.password; this.token = b.token; this.useOldRequestStyle = b.useOldRequestStyle; this.maxControlLine = b.maxControlLine; this.bufferSize = b.bufferSize; this.noEcho = b.noEcho; this.noHeaders = b.noHeaders; this.noNoResponders = b.noNoResponders; this.clientSideLimitChecks = b.clientSideLimitChecks; this.supportUTF8Subjects = b.supportUTF8Subjects; this.inboxPrefix = b.inboxPrefix; this.traceConnection = b.traceConnection; this.maxMessagesInOutgoingQueue = b.maxMessagesInOutgoingQueue; this.discardMessagesWhenOutgoingQueueFull = b.discardMessagesWhenOutgoingQueueFull; this.authHandler = b.authHandler; this.reconnectDelayHandler = b.reconnectDelayHandler; this.errorListener = b.errorListener; this.timeTraceLogger = b.timeTraceLogger; this.connectionListener = b.connectionListener; this.statisticsCollector = b.statisticsCollector; this.dataPortType = b.dataPortType; this.trackAdvancedStats = b.trackAdvancedStats; this.executor = b.executor; this.httpRequestInterceptors = b.httpRequestInterceptors; this.proxy = b.proxy; this.ignoreDiscoveredServers = b.ignoreDiscoveredServers; this.tlsFirst = b.tlsFirst; this.useTimeoutException = b.useTimeoutException; this.useDispatcherWithExecutor = b.useDispatcherWithExecutor; this.serverPool = b.serverPool; this.dispatcherFactory = b.dispatcherFactory; } // ---------------------------------------------------------------------------------------------------- // GETTERS // ---------------------------------------------------------------------------------------------------- /** * @return the executor, see {@link Builder#executor(ExecutorService) executor()} in the builder doc */ public ExecutorService getExecutor() { return this.executor; } /** * @return the list of HttpRequest interceptors. */ public List> getHttpRequestInterceptors() { return null == this.httpRequestInterceptors ? Collections.emptyList() : Collections.unmodifiableList(this.httpRequestInterceptors); } /** * @return the proxy to used for all sockets. */ public Proxy getProxy() { return this.proxy; } /** * @return the error listener. Will be an instance of ErrorListenerLoggerImpl if not user supplied. See {@link Builder#errorListener(ErrorListener) errorListener()} in the builder doc */ public ErrorListener getErrorListener() { return this.errorListener; } /** * If the user provided a TimeTraceLogger, it's returned here. * If the user set traceConnection but did not supply their own, the original time trace logging will occur * If the user did not provide a TimeTraceLogger and did not set traceConnection, this will be a no-op implementation. * @return the time trace logger */ public TimeTraceLogger getTimeTraceLogger() { return this.timeTraceLogger; } /** * @return the connection listener, or null, see {@link Builder#connectionListener(ConnectionListener) connectionListener()} in the builder doc */ public ConnectionListener getConnectionListener() { return this.connectionListener; } /** * @return the statistics collector, or null, see {@link Builder#statisticsCollector(StatisticsCollector) statisticsCollector()} in the builder doc */ public StatisticsCollector getStatisticsCollector() { return this.statisticsCollector; } /** * @return the auth handler, or null, see {@link Builder#authHandler(AuthHandler) authHandler()} in the builder doc */ public AuthHandler getAuthHandler() { return this.authHandler; } /** * @return the reconnection delay handler, or null, see {@link Builder#reconnectDelayHandler(ReconnectDelayHandler) reconnectDelayHandler()} in the builder doc */ public ReconnectDelayHandler getReconnectDelayHandler() { return this.reconnectDelayHandler; } /** * @return the dataport type for connections created by this options object, see {@link Builder#dataPortType(String) dataPortType()} in the builder doc */ public String getDataPortType() { return this.dataPortType; } /** * @return the data port described by these options */ public DataPort buildDataPort() { DataPort dp; if (dataPortType.equals(DEFAULT_DATA_PORT_TYPE)) { if (socketWriteTimeout == null) { dp = new SocketDataPort(); } else { dp = new SocketDataPortWithWriteTimeout(); } } else { dp = (DataPort) Options.createInstanceOf(dataPortType); } dp.afterConstruct(this); return dp; } /** * @return the servers configured in options, see {@link Builder#servers(String[]) servers()} in the builder doc */ public List getServers() { List list = new ArrayList<>(); for (NatsUri nuri : natsServerUris) { list.add(nuri.getUri()); } return list; } /** * @return the servers configured in options, see {@link Builder#servers(String[]) servers()} in the builder doc */ public List getNatsServerUris() { return natsServerUris; } /** * @return the servers as given to the options, since the servers are normalized */ public List getUnprocessedServers() { return unprocessedServers; } /** * @return should we turn off randomization for server connection attempts, see {@link Builder#noRandomize() noRandomize()} in the builder doc */ public boolean isNoRandomize() { return noRandomize; } /** * @return should we resolve hostnames for server connection attempts, see {@link Builder#noResolveHostnames() noResolveHostnames()} in the builder doc */ public boolean isNoResolveHostnames() { return noResolveHostnames; } /** * @return should complete with exception futures for requests that get no responders instead of cancelling the future, see {@link Builder#reportNoResponders() reportNoResponders()} in the builder doc */ public boolean isReportNoResponders() { return reportNoResponders; } /** * @return the connectionName, see {@link Builder#connectionName(String) connectionName()} in the builder doc */ public String getConnectionName() { return connectionName; } /** * @return are we in verbose mode, see {@link Builder#verbose() verbose()} in the builder doc */ public boolean isVerbose() { return verbose; } /** * @return is echo-ing disabled, see {@link Builder#noEcho() noEcho()} in the builder doc */ public boolean isNoEcho() { return noEcho; } /** * @return are headers disabled, see {@link Builder#noHeaders() noHeaders()} in the builder doc */ public boolean isNoHeaders() { return noHeaders; } /** * @return is NoResponders ignored disabled, see {@link Builder#noNoResponders() noNoResponders()} in the builder doc */ public boolean isNoNoResponders() { return noNoResponders; } /** * @return clientSideLimitChecks flag */ public boolean clientSideLimitChecks() { return clientSideLimitChecks; } /** * @return whether utf8 subjects are supported, see {@link Builder#supportUTF8Subjects() supportUTF8Subjects()} in the builder doc. */ public boolean supportUTF8Subjects() { return supportUTF8Subjects; } /** * @return are we using pedantic protocol, see {@link Builder#pedantic() pedantic()} in the builder doc */ public boolean isPedantic() { return pedantic; } /** * @return should we track advanced stats, see {@link Builder#turnOnAdvancedStats() turnOnAdvancedStats()} in the builder doc */ public boolean isTrackAdvancedStats() { return trackAdvancedStats; } /** * If isTraceConnection is true, the user provided a TimeTraceLogger or manually called traceConnection in the builder * @return should we trace the connection? */ public boolean isTraceConnection() { return traceConnection; } /** * @return the maximum length of a control line, see {@link Builder#maxControlLine(int) maxControlLine()} in the builder doc */ public int getMaxControlLine() { return maxControlLine; } /** * * @return true if there is an sslContext for these Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc */ public boolean isTLSRequired() { return sslContext != null; } /** * @return the sslContext, see {@link Builder#secure() secure()} in the builder doc */ public SSLContext getSslContext() { return sslContext; } /** * @return the maxReconnect attempts to make before failing, see {@link Builder#maxReconnects(int) maxReconnects()} in the builder doc */ public int getMaxReconnect() { return maxReconnect; } /** * @return the reconnectWait, used between reconnect attempts, see {@link Builder#reconnectWait(Duration) reconnectWait()} in the builder doc */ public Duration getReconnectWait() { return reconnectWait; } /** * @return the reconnectJitter, used between reconnect attempts to vary the reconnect wait, see {@link Builder#reconnectJitter(Duration) reconnectJitter()} in the builder doc */ public Duration getReconnectJitter() { return reconnectJitter; } /** * @return the reconnectJitterTls, used between reconnect attempts to vary the reconnect wait whe using tls/secure, see {@link Builder#reconnectJitterTls(Duration) reconnectJitterTls()} in the builder doc */ public Duration getReconnectJitterTls() { return reconnectJitterTls; } /** * @return the connectionTimeout, see {@link Builder#connectionTimeout(Duration) connectionTimeout()} in the builder doc */ public Duration getConnectionTimeout() { return connectionTimeout; } /** * @return the socketReadTimeoutMillis, see {@link Builder#socketReadTimeoutMillis(int) socketReadTimeoutMillis} in the builder doc */ public int getSocketReadTimeoutMillis() { return socketReadTimeoutMillis; } /** * @return the socketWriteTimeout, see {@link Builder#socketWriteTimeout(long) socketWriteTimeout} in the builder doc */ public Duration getSocketWriteTimeout() { return socketWriteTimeout; } /** * @return the socket so linger number of seconds, see {@link Builder#socketSoLinger(int) socketSoLinger()} in the builder doc */ public int getSocketSoLinger() { return socketSoLinger; } /** * @return the pingInterval, see {@link Builder#pingInterval(Duration) pingInterval()} in the builder doc */ public Duration getPingInterval() { return pingInterval; } /** * @return the request cleanup interval, see {@link Builder#requestCleanupInterval(Duration) requestCleanupInterval()} in the builder doc */ public Duration getRequestCleanupInterval() { return requestCleanupInterval; } /** * @return the maxPingsOut to limit the number of pings on the wire, see {@link Builder#maxPingsOut(int) maxPingsOut()} in the builder doc */ public int getMaxPingsOut() { return maxPingsOut; } /** * @return the reconnectBufferSize, to limit the amount of data held during * reconnection attempts, see {@link Builder#reconnectBufferSize(long) reconnectBufferSize()} in the builder doc */ public long getReconnectBufferSize() { return reconnectBufferSize; } /** * @return the default size for buffers in the connection code, see {@link Builder#bufferSize(int) bufferSize()} in the builder doc */ public int getBufferSize() { return bufferSize; } /** * @deprecated converts the char array to a string, use getUserNameChars instead for more security * @return the username to use for basic authentication, see {@link Builder#userInfo(String, String) userInfo()} in the builder doc */ @Deprecated public String getUsername() { return username == null ? null : new String(username); } /** * @return the username to use for basic authentication, see {@link Builder#userInfo(String, String) userInfo()} in the builder doc */ public char[] getUsernameChars() { return username; } /** * @deprecated converts the char array to a string, use getPasswordChars instead for more security * @return the password to use for basic authentication, see {@link Builder#userInfo(String, String) userInfo()} in the builder doc */ @Deprecated public String getPassword() { return password == null ? null : new String(password); } /** * @return the password to use for basic authentication, see {@link Builder#userInfo(String, String) userInfo()} in the builder doc */ public char[] getPasswordChars() { return password; } /** * @deprecated converts the char array to a string, use getTokenChars instead for more security * @return the token to be used for token-based authentication, see {@link Builder#token(String) token()} in the builder doc */ @Deprecated public String getToken() { return token == null ? null : new String(token); } /** * @return the token to be used for token-based authentication, see {@link Builder#token(String) token()} in the builder doc */ public char[] getTokenChars() { return token; } /** * @return the flag to turn on old style requests, see {@link Builder#oldRequestStyle() oldStyleRequest()} in the builder doc */ public boolean isOldRequestStyle() { return useOldRequestStyle; } /** * @return the inbox prefix to use for requests, see {@link Builder#inboxPrefix(String) inboxPrefix()} in the builder doc */ public String getInboxPrefix() { return inboxPrefix; } /** * @return the maximum number of messages in the outgoing queue, see {@link Builder#maxMessagesInOutgoingQueue(int) * maxMessagesInOutgoingQueue(int)} in the builder doc */ public int getMaxMessagesInOutgoingQueue() { return maxMessagesInOutgoingQueue; } /** * @return should we discard messages when the outgoing queue is full, see {@link Builder#discardMessagesWhenOutgoingQueueFull() * discardMessagesWhenOutgoingQueueFull()} in the builder doc */ public boolean isDiscardMessagesWhenOutgoingQueueFull() { return discardMessagesWhenOutgoingQueueFull; } /** * Get whether to ignore discovered servers * @return the flag */ public boolean isIgnoreDiscoveredServers() { return ignoreDiscoveredServers; } /** * Get whether to do tls first * @return the flag */ public boolean isTlsFirst() { return tlsFirst; } /** * Get whether to throw {@link java.util.concurrent.TimeoutException} on timeout instead of {@link java.util.concurrent.CancellationException}. * @return the flag */ public boolean useTimeoutException() { return useTimeoutException; } public boolean useDispatcherWithExecutor() { return useDispatcherWithExecutor; } /** * Get the ServerPool implementation. If null, a default implementation is used. * @return the ServerPool implementation */ public ServerPool getServerPool() { return serverPool; } /** * Get the DispatcherFactory implementation. If null, a default implementation is used. * @return the DispatcherFactory implementation */ public DispatcherFactory getDispatcherFactory() { return dispatcherFactory; } public URI createURIForServer(String serverURI) throws URISyntaxException { return new NatsUri(serverURI).getUri(); } /** * Create the options string sent with the connect message. * If includeAuth is true the auth information is included: * If the server URIs have auth info it is used. Otherwise, the userInfo is used. * @param serverURI the current server uri * @param includeAuth tells the options to build a connection string that includes auth information * @param nonce if the client is supposed to sign the nonce for authentication * @return the options String, basically JSON */ public CharBuffer buildProtocolConnectOptionsString(String serverURI, boolean includeAuth, byte[] nonce) { CharBuffer connectString = CharBuffer.allocate(this.maxControlLine); connectString.append("{"); appendOption(connectString, Options.OPTION_LANG, Nats.CLIENT_LANGUAGE, true, false); appendOption(connectString, Options.OPTION_VERSION, Nats.CLIENT_VERSION, true, true); if (this.connectionName != null) { appendOption(connectString, Options.OPTION_NAME, this.connectionName, true, true); } appendOption(connectString, Options.OPTION_PROTOCOL, "1", false, true); appendOption(connectString, Options.OPTION_VERBOSE, String.valueOf(this.isVerbose()), false, true); appendOption(connectString, Options.OPTION_PEDANTIC, String.valueOf(this.isPedantic()), false, true); appendOption(connectString, Options.OPTION_TLS_REQUIRED, String.valueOf(this.isTLSRequired()), false, true); appendOption(connectString, Options.OPTION_ECHO, String.valueOf(!this.isNoEcho()), false, true); appendOption(connectString, Options.OPTION_HEADERS, String.valueOf(!this.isNoHeaders()), false, true); appendOption(connectString, Options.OPTION_NORESPONDERS, String.valueOf(!this.isNoNoResponders()), false, true); if (includeAuth) { if (nonce != null && this.getAuthHandler() != null) { char[] nkey = this.getAuthHandler().getID(); byte[] sig = this.getAuthHandler().sign(nonce); char[] jwt = this.getAuthHandler().getJWT(); if (sig == null) { sig = new byte[0]; } if (jwt == null) { jwt = new char[0]; } if (nkey == null) { nkey = new char[0]; } String encodedSig = base64UrlEncodeToString(sig); appendOption(connectString, Options.OPTION_NKEY, nkey, true); appendOption(connectString, Options.OPTION_SIG, encodedSig, true, true); appendOption(connectString, Options.OPTION_JWT, jwt, true); } String uriUser = null; String uriPass = null; String uriToken = null; // Values from URI override options try { URI uri = this.createURIForServer(serverURI); String userInfo = uri.getRawUserInfo(); if (userInfo != null) { int at = userInfo.indexOf(":"); if (at == -1) { uriToken = uriDecode(userInfo); } else { uriUser = uriDecode(userInfo.substring(0, at)); uriPass = uriDecode(userInfo.substring(at + 1)); } } } catch (URISyntaxException e) { // the createURIForServer call is the one that potentially throws this // uriUser, uriPass and uriToken will already be null } if (uriUser != null) { appendOption(connectString, Options.OPTION_USER, jsonEncode(uriUser), true, true); } else if (this.username != null) { appendOption(connectString, Options.OPTION_USER, jsonEncode(this.username), true, true); } if (uriPass != null) { appendOption(connectString, Options.OPTION_PASSWORD, jsonEncode(uriPass), true, true); } else if (this.password != null) { appendOption(connectString, Options.OPTION_PASSWORD, jsonEncode(this.password), true, true); } if (uriToken != null) { appendOption(connectString, Options.OPTION_AUTH_TOKEN, uriToken, true, true); } else if (this.token != null) { appendOption(connectString, Options.OPTION_AUTH_TOKEN, this.token, true); } } connectString.append("}"); connectString.flip(); return connectString; } // ---------------------------------------------------------------------------------------------------- // HELPER FUNCTIONS // ---------------------------------------------------------------------------------------------------- private static void appendOption(CharBuffer builder, String key, String value, boolean quotes, boolean comma) { _appendStart(builder, key, quotes, comma); builder.append(value); _appendOptionEnd(builder, quotes); } @SuppressWarnings("SameParameterValue") private static void appendOption(CharBuffer builder, String key, char[] value, boolean comma) { _appendStart(builder, key, true, comma); builder.put(value); _appendOptionEnd(builder, true); } private static void _appendStart(CharBuffer builder, String key, boolean quotes, boolean comma) { if (comma) { builder.append(','); } builder.append('"'); builder.append(key); builder.append('"'); builder.append(':'); _appendOptionEnd(builder, quotes); } private static void _appendOptionEnd(CharBuffer builder, boolean quotes) { if (quotes) { builder.append('"'); } } private static String getPropertyValue(Properties props, String key) { String value = emptyAsNull(props.getProperty(key)); if (value != null) { return value; } if (key.startsWith(PFX)) { // if the key starts with the PFX, check the non PFX return emptyAsNull(props.getProperty(key.substring(PFX_LEN))); } // otherwise check with the PFX value = emptyAsNull(props.getProperty(PFX + key)); if (value == null && key.contains("_")) { // addressing where underscore was used in a key value instead of dot return getPropertyValue(props, key.replace("_", ".")); } return value; } private static void stringProperty(Properties props, String key, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value != null) { consumer.accept(value); } } private static void charArrayProperty(Properties props, String key, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value != null) { consumer.accept(value.toCharArray()); } } private static void booleanProperty(Properties props, String key, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value != null) { consumer.accept(Boolean.parseBoolean(value)); } } private static void intProperty(Properties props, String key, int defaultValue, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value == null) { consumer.accept(defaultValue); } else { consumer.accept(Integer.parseInt(value)); } } private static void intGtEqZeroProperty(Properties props, String key, int defaultValue, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value == null) { consumer.accept(defaultValue); } else { int i = Integer.parseInt(value); if (i < 0) { consumer.accept(defaultValue); } else { consumer.accept(i); } } } private static void longProperty(Properties props, String key, long defaultValue, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value == null) { consumer.accept(defaultValue); } else { consumer.accept(Long.parseLong(value)); } } private static void durationProperty(Properties props, String key, Duration defaultValue, java.util.function.Consumer consumer) { String value = getPropertyValue(props, key); if (value == null) { consumer.accept(defaultValue); } else { try { Duration d = Duration.parse(value); if (d.toNanos() < 0) { consumer.accept(defaultValue); } else { consumer.accept(d); } } catch (DateTimeParseException pe) { int ms = Integer.parseInt(value); if (ms < 0) { consumer.accept(defaultValue); } else { consumer.accept(Duration.ofMillis(ms)); } } } } private static void classnameProperty(Properties props, String key, java.util.function.Consumer consumer) { stringProperty(props, key, className -> consumer.accept(createInstanceOf(className))); } private static Object createInstanceOf(String className) { try { Class clazz = Class.forName(className); Constructor constructor = clazz.getConstructor(); return constructor.newInstance(); } catch (Exception e) { throw new IllegalArgumentException(e); } } }