reactor.io.net.Spec Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2011-2015 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.io.net;
import reactor.Environment;
import reactor.bus.spec.DispatcherComponentSpec;
import reactor.core.Dispatcher;
import reactor.core.support.Assert;
import reactor.fn.Consumer;
import reactor.fn.Function;
import reactor.fn.Supplier;
import reactor.fn.tuple.Tuple;
import reactor.fn.tuple.Tuple2;
import reactor.io.buffer.Buffer;
import reactor.io.codec.Codec;
import reactor.io.net.config.ClientSocketOptions;
import reactor.io.net.config.ServerSocketOptions;
import reactor.io.net.config.SslOptions;
import reactor.io.net.http.HttpChannel;
import reactor.io.net.http.HttpServer;
import reactor.io.net.tcp.TcpServer;
import reactor.io.net.udp.DatagramServer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Specifications used to build client and servers.
*
* @author Stephane Maldini
* @author Jon Brisbin
* @since 2.0
*/
public interface Spec {
Function NOOP_DECODER = new Function() {
@Override
public Object apply(Object o) {
return o;
}
};
Codec NOOP_CODEC = new Codec() {
@Override
public Function decoder(Consumer next) {
return NOOP_DECODER;
}
@Override
public Object apply(Object o) {
return o;
}
};
//
// Client and Server Specifications
//
abstract class PeerSpec,
S extends PeerSpec,
N extends ReactorPeer>
extends DispatcherComponentSpec {
protected ServerSocketOptions options = new ServerSocketOptions();
protected InetSocketAddress listenAddress;
protected Codec codec;
/**
* Set the common {@link reactor.io.net.config.ServerSocketOptions} for channels made in this server.
*
* @param options The options to set when new channels are made.
* @return {@literal this}
*/
@SuppressWarnings("unchecked")
public S options(@Nonnull ServerSocketOptions options) {
Assert.notNull(options, "ServerSocketOptions cannot be null.");
this.options = options;
return (S) this;
}
/**
* The port on which this server should listen, assuming it should bind to all available addresses.
*
* @param port The port to listen on.
* @return {@literal this}
*/
@SuppressWarnings("unchecked")
public S listen(int port) {
return listen(new InetSocketAddress(port));
}
/**
* The host and port on which this server should listen.
*
* @param host The host to bind to.
* @param port The port to listen on.
* @return {@literal this}
*/
@SuppressWarnings("unchecked")
public S listen(String host, int port) {
if (null == host) {
host = "localhost";
}
return listen(new InetSocketAddress(host, port));
}
/**
* The {@link java.net.InetSocketAddress} on which this server should listen.
*
* @param listenAddress the listen address
* @return {@literal this}
*/
@SuppressWarnings("unchecked")
public S listen(InetSocketAddress listenAddress) {
this.listenAddress = listenAddress;
return (S) this;
}
/**
* The {@link reactor.io.codec.Codec} to use to encode and decode data.
*
* @param codec The codec to use.
* @return {@literal this}
*/
@SuppressWarnings("unchecked")
public S codec(@Nonnull Codec codec) {
Assert.notNull(codec, "Codec cannot be null.");
this.codec = codec;
return (S) this;
}
/**
* Bypass any Reactor Buffer encoding for received data
*
* @param israw to enable raw data transfer from the server (e.g. ByteBuf from Netty).
* @return this
*/
@SuppressWarnings("unchecked")
public S rawData(boolean israw) {
if(israw){
this.codec = NOOP_CODEC;
}
return (S) this;
}
}
/**
* A helper class for specifying a {@code TcpClient}
*
* @param
* The type that will be received by the client
* @param
* The type that will be sent by the client
*
* @author Jon Brisbin
* @author Stephane Maldini
*/
class TcpClientSpec extends DispatcherComponentSpec, reactor.io.net.tcp.TcpClient> {
private final Constructor clientImplConstructor;
private Supplier connectAddress;
private ClientSocketOptions options = new ClientSocketOptions();
private SslOptions sslOptions = null;
private Codec codec;
/**
* Create a {@code TcpClient.Spec} using the given implementation class.
*
* @param clientImpl
* The concrete implementation of {@link reactor.io.net.tcp.TcpClient} to instantiate.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
TcpClientSpec(@Nonnull Class clientImpl) {
Assert.notNull(clientImpl, "TcpClient implementation class cannot be null.");
try {
this.clientImplConstructor = (Constructor) clientImpl.getDeclaredConstructor(
Environment.class,
Dispatcher.class,
Supplier.class,
ClientSocketOptions.class,
SslOptions.class,
Codec.class
);
this.clientImplConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"No public constructor found that matches the signature of the one found in the TcpClient class.");
}
}
/**
* Set the common {@link reactor.io.net.config.ClientSocketOptions} for connections made in this client.
*
* @param options
* The socket options to apply to new connections.
*
* @return {@literal this}
*/
public TcpClientSpec options(ClientSocketOptions options) {
this.options = options;
return this;
}
/**
* Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the
* default).
*
* @param sslOptions
* The options to set when configuring SSL
*
* @return {@literal this}
*/
public TcpClientSpec ssl(@Nullable SslOptions sslOptions) {
this.sslOptions = sslOptions;
return this;
}
/**
* The host and port to which this client should connect.
*
* @param host
* The host to connect to.
* @param port
* The port to connect to.
*
* @return {@literal this}
*/
public TcpClientSpec connect(final @Nonnull String host, final int port) {
return connect(new Supplier() {
@Override
public InetSocketAddress get() {
return new InetSocketAddress(host, port);
}
});
}
/**
* The address to which this client should connect.
*
* @param connectAddress
* The address to connect to.
*
* @return {@literal this}
*/
public TcpClientSpec connect(final @Nonnull InetSocketAddress connectAddress) {
return connect(new Supplier() {
@Override
public InetSocketAddress get() {
return connectAddress;
}
});
}
/**
* The eventual address to which this client should connect.
*
* @param connectAddress
* The address to connect to.
*
* @return {@literal this}
*/
public TcpClientSpec connect(@Nonnull Supplier connectAddress) {
Assert.isNull(this.connectAddress, "Connect address is already set.");
this.connectAddress = connectAddress;
return this;
}
/**
* The {@link reactor.io.codec.Codec} to use to encode and decode data.
*
* @param codec
* The codec to use.
*
* @return {@literal this}
*/
public TcpClientSpec codec(@Nullable Codec codec) {
Assert.isNull(this.codec, "Codec has already been set.");
this.codec = codec;
return this;
}
/**
* Bypass any Reactor Buffer encoding for received data
*
* @param israw to enable raw data transfer from the server (e.g. ByteBuf from Netty).
* @return this
*/
@SuppressWarnings("unchecked")
public TcpClientSpec rawData(boolean israw) {
if(israw){
this.codec = NOOP_CODEC;
}
return this;
}
@Override
@SuppressWarnings("unchecked")
protected reactor.io.net.tcp.TcpClient configure(Dispatcher dispatcher, Environment environment) {
try {
return clientImplConstructor.newInstance(
environment,
dispatcher,
connectAddress,
options,
sslOptions,
codec
);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
/**
* A TcpServerSpec is used to specify a TcpServer
*
* @param
* The type that will be received by this client
* @param
* The type that will be sent by this client
*
* @author Jon Brisbin
* @author Stephane Maldini
*/
class TcpServerSpec
extends PeerSpec, TcpServerSpec, TcpServer> {
private final Constructor serverImplConstructor;
private SslOptions sslOptions = null;
/**
* Create a {@code TcpServer.Spec} using the given implementation class.
*
* @param serverImpl
* The concrete implementation of {@link reactor.io.net.tcp.TcpServer} to instantiate.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
TcpServerSpec(@Nonnull Class serverImpl) {
Assert.notNull(serverImpl, "TcpServer implementation class cannot be null.");
try {
this.serverImplConstructor = serverImpl.getDeclaredConstructor(
Environment.class,
Dispatcher.class,
InetSocketAddress.class,
ServerSocketOptions.class,
SslOptions.class,
Codec.class
);
this.serverImplConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"No public constructor found that matches the signature of the one found in the TcpServer class.");
}
}
/**
* Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the
* default).
*
* @param sslOptions
* The options to set when configuring SSL
*
* @return {@literal this}
*/
public TcpServerSpec ssl(@Nullable SslOptions sslOptions) {
this.sslOptions = sslOptions;
return this;
}
@SuppressWarnings("unchecked")
@Override
protected reactor.io.net.tcp.TcpServer configure(Dispatcher dispatcher, Environment env) {
try {
return serverImplConstructor.newInstance(
env,
dispatcher,
listenAddress,
options,
sslOptions,
codec
);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
/**
* @author Jon Brisbin
* @author Stephane Maldini
*/
class DatagramServerSpec
extends PeerSpec, DatagramServerSpec, DatagramServer> {
protected final Constructor serverImplCtor;
private NetworkInterface multicastInterface;
DatagramServerSpec(Class serverImpl) {
Assert.notNull(serverImpl, "NetServer implementation class cannot be null.");
try {
this.serverImplCtor = serverImpl.getDeclaredConstructor(
Environment.class,
Dispatcher.class,
InetSocketAddress.class,
NetworkInterface.class,
ServerSocketOptions.class,
Codec.class
);
this.serverImplCtor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"No public constructor found that matches the signature of the one found in the DatagramServer class.");
}
}
/**
* Set the interface to use for multicast.
*
* @param iface
* the {@link java.net.NetworkInterface} to use for multicast.
*
* @return {@literal this}
*/
public DatagramServerSpec multicastInterface(NetworkInterface iface) {
this.multicastInterface = iface;
return this;
}
@SuppressWarnings("unchecked")
@Override
protected reactor.io.net.udp.DatagramServer configure(Dispatcher dispatcher, Environment environment) {
try {
return serverImplCtor.newInstance(
environment,
dispatcher,
listenAddress,
multicastInterface,
options,
codec
);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
/**
* A HttpServer Spec is used to specify an HttpServer
*
* @param
* The type that will be received by this client
* @param
* The type that will be sent by this client
*
* @author Jon Brisbin
* @author Stephane Maldini
*/
class HttpServerSpec
extends PeerSpec, HttpServerSpec, HttpServer> {
private final Constructor serverImplConstructor;
private SslOptions sslOptions = null;
/**
* Create a {@code TcpServer.Spec} using the given implementation class.
*
* @param serverImpl
* The concrete implementation of {@link reactor.io.net.http.HttpClient} to instantiate.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
HttpServerSpec(@Nonnull Class serverImpl) {
Assert.notNull(serverImpl, "TcpServer implementation class cannot be null.");
try {
this.serverImplConstructor = serverImpl.getDeclaredConstructor(
Environment.class,
Dispatcher.class,
InetSocketAddress.class,
ServerSocketOptions.class,
SslOptions.class,
Codec.class
);
this.serverImplConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"No public constructor found that matches the signature of the one found in the TcpServer class.");
}
}
/**
* Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the
* default).
*
* @param sslOptions
* The options to set when configuring SSL
*
* @return {@literal this}
*/
public HttpServerSpec ssl(@Nullable SslOptions sslOptions) {
this.sslOptions = sslOptions;
return this;
}
@SuppressWarnings("unchecked")
@Override
protected reactor.io.net.http.HttpServer configure(Dispatcher dispatcher, Environment env) {
try {
return serverImplConstructor.newInstance(
env,
dispatcher,
listenAddress,
options,
sslOptions,
codec
);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
/**
* A helper class for specifying a {@code HttpClient}
*
* @param
* The type that will be received by the client
* @param
* The type that will be sent by the client
*
* @author Stephane Maldini
*/
class HttpClientSpec extends DispatcherComponentSpec, reactor.io.net.http.HttpClient> {
private final Constructor clientImplConstructor;
private Supplier connectAddress;
private ClientSocketOptions options = new ClientSocketOptions();
private SslOptions sslOptions = null;
private Codec codec;
/**
* Create a {@code TcpClient.Spec} using the given implementation class.
*
* @param clientImpl
* The concrete implementation of {@link reactor.io.net.http.HttpClient} to instantiate.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
HttpClientSpec(@Nonnull Class clientImpl) {
Assert.notNull(clientImpl, "TcpClient implementation class cannot be null.");
try {
this.clientImplConstructor = (Constructor) clientImpl.getDeclaredConstructor(
Environment.class,
Dispatcher.class,
Supplier.class,
ClientSocketOptions.class,
SslOptions.class,
Codec.class
);
this.clientImplConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"No public constructor found that matches the signature of the one found in the TcpClient class.");
}
}
/**
* Set the common {@link reactor.io.net.config.ClientSocketOptions} for connections made in this client.
*
* @param options
* The socket options to apply to new connections.
*
* @return {@literal this}
*/
public HttpClientSpec options(ClientSocketOptions options) {
this.options = options;
return this;
}
/**
* Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the
* default).
*
* @param sslOptions
* The options to set when configuring SSL
*
* @return {@literal this}
*/
public HttpClientSpec ssl(@Nullable SslOptions sslOptions) {
this.sslOptions = sslOptions;
return this;
}
/**
* The host and port to which this client should connect.
*
* @param host
* The host to connect to.
* @param port
* The port to connect to.
*
* @return {@literal this}
*/
public HttpClientSpec connect(@Nonnull final String host, final int port) {
return connect(new Supplier() {
@Override
public InetSocketAddress get() {
return new InetSocketAddress(host, port);
}
});
}
/**
* The address to which this client should connect.
*
* @param connectAddress
* The address to connect to.
*
* @return {@literal this}
*/
public HttpClientSpec connect(@Nonnull final InetSocketAddress connectAddress) {
return connect(new Supplier() {
@Override
public InetSocketAddress get() {
return connectAddress;
}
});
}
/**
* The address to which this client should connect.
*
* @param connectAddress
* The address to connect to.
*
* @return {@literal this}
*/
public HttpClientSpec connect(@Nonnull Supplier connectAddress) {
Assert.isNull(this.connectAddress, "Connect address is already set.");
this.connectAddress = connectAddress;
return this;
}
/**
* The {@link reactor.io.codec.Codec} to use to encode and decode data.
*
* @param codec
* The codec to use.
*
* @return {@literal this}
*/
public HttpClientSpec codec(@Nullable Codec codec) {
Assert.isNull(this.codec, "Codec has already been set.");
this.codec = codec;
return this;
}
@Override
@SuppressWarnings("unchecked")
protected reactor.io.net.http.HttpClient configure(Dispatcher dispatcher, Environment environment) {
try {
return clientImplConstructor.newInstance(
environment,
dispatcher,
connectAddress,
options,
sslOptions,
codec
);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
/**
* A helper class for configure a new {@code Reconnect}.
*
*/
class IncrementalBackoffReconnect implements Supplier {
public static final long DEFAULT_INTERVAL = 5000;
public static final long DEFAULT_MULTIPLIER = 1;
public static final long DEFAULT_MAX_ATTEMPTS = -1;
private final List addresses;
private long interval;
private long multiplier;
private long maxInterval;
private long maxAttempts;
/**
*
*/
IncrementalBackoffReconnect() {
this.addresses = new LinkedList();
this.interval = DEFAULT_INTERVAL;
this.multiplier = DEFAULT_MULTIPLIER;
this.maxInterval = Long.MAX_VALUE;
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
}
/**
* Set the reconnection interval.
*
* @param interval the period reactor waits between attemps to reconnect disconnected peers
* @return {@literal this}
*/
public IncrementalBackoffReconnect interval(long interval) {
this.interval = interval;
return this;
}
/**
* Set the maximum reconnection interval that will be applied if the multiplier
* is set to a value greather than one.
*
* @param maxInterval
* @return {@literal this}
*/
public IncrementalBackoffReconnect maxInterval(long maxInterval) {
this.maxInterval = maxInterval;
return this;
}
/**
* Set the backoff multiplier.
*
* @param multiplier
* @return {@literal this}
*/
public IncrementalBackoffReconnect multiplier(long multiplier) {
this.multiplier = multiplier;
return this;
}
/**
* Sets the number of time that Reactor will attempt to connect or reconnect
* before giving up.
*
* @param maxAttempts The max number of attempts made before failing.
* @return {@literal this}
*/
public IncrementalBackoffReconnect maxAttempts(long maxAttempts) {
this.maxAttempts = maxAttempts;
return this;
}
/**
* Add an address to the pool of addresses.
*
* @param address
* @return {@literal this}
*/
public IncrementalBackoffReconnect address(InetSocketAddress address) {
this.addresses.add(address);
return this;
}
/**
* Add an address to the pool of addresses.
*
* @param host
* @param port
* @return {@literal this}
*/
public IncrementalBackoffReconnect address(String host, int port) {
this.addresses.add(new InetSocketAddress(host, port));
return this;
}
@Override
public Reconnect get() {
final AtomicInteger count = new AtomicInteger();
final int len = addresses.size();
final Supplier endpoints = new Supplier() {
@Override public InetSocketAddress get() {
return addresses.get(count.getAndIncrement() % len);
}
};
return new Reconnect() {
public Tuple2 reconnect(InetSocketAddress currentAddress, int attempt) {
Tuple2 rv = null;
synchronized (IncrementalBackoffReconnect.this) {
if (!addresses.isEmpty()) {
if (IncrementalBackoffReconnect.this.maxAttempts == -1 ||
IncrementalBackoffReconnect.this.maxAttempts > attempt) {
rv = Tuple.of(endpoints.get(), determineInterval(attempt));
}
} else {
rv = Tuple.of(currentAddress, determineInterval(attempt));
}
}
return rv;
}
};
}
/**
* Determine the period in milliseconds between reconnection attempts.
*
* @param attempt the number of times a reconnection has been attempted
* @return the reconnection period
*/
public long determineInterval(int attempt) {
return (multiplier > 1) ? Math.min(maxInterval, interval * attempt) : interval;
}
}
}