nl.topicus.jdbc.shaded.io.grpc.netty.NettyChannelBuilder Maven / Gradle / Ivy
Show all versions of spanner-jdbc Show documentation
/*
* Copyright 2014, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package nl.topicus.jdbc.shaded.io.grpc.nl.topicus.jdbc.shaded.net.y;
import static nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Preconditions.checkArgument;
import static nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Preconditions.checkNotNull;
import static nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Preconditions.checkState;
import static nl.topicus.jdbc.shaded.io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
import static nl.topicus.jdbc.shaded.io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIME_NANOS;
import static nl.topicus.jdbc.shaded.io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.annotations.VisibleForTesting;
import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Preconditions;
import nl.topicus.jdbc.shaded.com.google.errorprone.annotations.CanIgnoreReturnValue;
import nl.topicus.jdbc.shaded.io.grpc.Attributes;
import nl.topicus.jdbc.shaded.io.grpc.ExperimentalApi;
import nl.topicus.jdbc.shaded.io.grpc.Internal;
import nl.topicus.jdbc.shaded.io.grpc.NameResolver;
import nl.topicus.jdbc.shaded.io.grpc.internal.AbstractManagedChannelImplBuilder;
import nl.topicus.jdbc.shaded.io.grpc.internal.AtomicBackoff;
import nl.topicus.jdbc.shaded.io.grpc.internal.ClientTransportFactory;
import nl.topicus.jdbc.shaded.io.grpc.internal.ConnectionClientTransport;
import nl.topicus.jdbc.shaded.io.grpc.internal.GrpcUtil;
import nl.topicus.jdbc.shaded.io.grpc.internal.KeepAliveManager;
import nl.topicus.jdbc.shaded.io.grpc.internal.SharedResourceHolder;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.channel.Channel;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.channel.ChannelOption;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.channel.EventLoopGroup;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.channel.socket.nio.NioSocketChannel;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.ssl.SslContext;
import java.nl.topicus.jdbc.shaded.net.InetSocketAddress;
import java.nl.topicus.jdbc.shaded.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import nl.topicus.jdbc.shaded.javax.annotation.CheckReturnValue;
import nl.topicus.jdbc.shaded.javax.annotation.Nullable;
import javax.nl.topicus.jdbc.shaded.net.ssl.SSLException;
/**
* A builder to help simplify construction of channels using the Netty transport.
*/
@ExperimentalApi("https://github.nl.topicus.jdbc.shaded.com.grpc/grpc-java/issues/1784")
@CanIgnoreReturnValue
public final class NettyChannelBuilder
extends AbstractManagedChannelImplBuilder {
public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB
private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
private final Map, Object> channelOptions =
new HashMap, Object>();
private NegotiationType negotiationType = NegotiationType.TLS;
private OverrideAuthorityChecker authorityChecker;
private Class extends Channel> channelType = NioSocketChannel.class;
@Nullable
private EventLoopGroup eventLoopGroup;
private SslContext sslContext;
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
private boolean keepAliveWithoutCalls;
private TransportCreationParamsFilterFactory dynamicParamsFactory;
/**
* Creates a new builder with the given server address. This factory method is primarily intended
* for using Netty Channel types other than SocketChannel. {@link #forAddress(String, int)} should
* generally be preferred over this method, since that API permits delaying DNS lookups and
* noticing changes to DNS.
*/
@CheckReturnValue
public static NettyChannelBuilder forAddress(SocketAddress serverAddress) {
return new NettyChannelBuilder(serverAddress);
}
/**
* Creates a new builder with the given host and port.
*/
@CheckReturnValue
public static NettyChannelBuilder forAddress(String host, int port) {
return new NettyChannelBuilder(host, port);
}
/**
* Creates a new builder with the given target string that will be resolved by
* {@link nl.topicus.jdbc.shaded.io.grpc.NameResolver}.
*/
@CheckReturnValue
public static NettyChannelBuilder forTarget(String target) {
return new NettyChannelBuilder(target);
}
@CheckReturnValue
NettyChannelBuilder(String host, int port) {
this(GrpcUtil.authorityFromHostAndPort(host, port));
}
@CheckReturnValue
NettyChannelBuilder(String target) {
super(target);
}
@CheckReturnValue
NettyChannelBuilder(SocketAddress address) {
super(address, getAuthorityFromAddress(address));
}
@CheckReturnValue
private static String getAuthorityFromAddress(SocketAddress address) {
if (address instanceof InetSocketAddress) {
InetSocketAddress inetAddress = (InetSocketAddress) address;
return GrpcUtil.authorityFromHostAndPort(inetAddress.getHostString(), inetAddress.getPort());
} else {
return address.toString();
}
}
/**
* Specifies the channel type to use, by default we use {@link NioSocketChannel}.
*/
public NettyChannelBuilder channelType(Class extends Channel> channelType) {
this.channelType = Preconditions.checkNotNull(channelType, "channelType");
return this;
}
/**
* Specifies a channel option. As the underlying channel as well as nl.topicus.jdbc.shaded.net.ork implementation may
* ignore this value applications should consider it a hint.
*/
public NettyChannelBuilder withOption(ChannelOption option, T value) {
channelOptions.put(option, value);
return this;
}
/**
* Sets the negotiation type for the HTTP/2 connection.
*
* Default: TLS
*/
public NettyChannelBuilder negotiationType(NegotiationType type) {
negotiationType = type;
return this;
}
/**
* Provides an EventGroupLoop to be used by the nl.topicus.jdbc.shaded.net.y transport.
*
*
It's an optional parameter. If the user has not provided an EventGroupLoop when the channel
* is built, the builder will use the default one which is static.
*
*
The channel won't take ownership of the given EventLoopGroup. It's caller's responsibility
* to shut it down when it's desired.
*/
public NettyChannelBuilder eventLoopGroup(@Nullable EventLoopGroup eventLoopGroup) {
this.eventLoopGroup = eventLoopGroup;
return this;
}
/**
* SSL/TLS context to use instead of the system default. It must have been configured with {@link
* GrpcSslContexts}, but options could have been overridden.
*/
public NettyChannelBuilder sslContext(SslContext sslContext) {
if (sslContext != null) {
checkArgument(sslContext.isClient(),
"Server SSL context can not be used for client channel");
GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator());
}
this.sslContext = sslContext;
return this;
}
/**
* Sets the flow control window in bytes. If not called, the default value
* is {@link #DEFAULT_FLOW_CONTROL_WINDOW}).
*/
public NettyChannelBuilder flowControlWindow(int flowControlWindow) {
checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
this.flowControlWindow = flowControlWindow;
return this;
}
/**
* Sets the max message size.
*
* @deprecated Use {@link #maxInboundMessageSize} instead
*/
@Deprecated
public NettyChannelBuilder maxMessageSize(int maxMessageSize) {
maxInboundMessageSize(maxMessageSize);
return this;
}
/**
* Sets the maximum size of header list allowed to be received on the channel. If not called,
* defaults to {@link GrpcUtil#DEFAULT_MAX_HEADER_LIST_SIZE}.
*/
public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) {
checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be > 0");
this.maxHeaderListSize = maxHeaderListSize;
return this;
}
/**
* Equivalent to using {@link #negotiationType(NegotiationType)} with {@code PLAINTEXT} or
* {@code PLAINTEXT_UPGRADE}.
*/
@Override
public NettyChannelBuilder usePlaintext(boolean skipNegotiation) {
if (skipNegotiation) {
negotiationType(NegotiationType.PLAINTEXT);
} else {
negotiationType(NegotiationType.PLAINTEXT_UPGRADE);
}
return this;
}
/**
* Enable keepalive with default delay and timeout.
*
* @deprecated Please use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
*/
@Deprecated
public final NettyChannelBuilder enableKeepAlive(boolean enable) {
if (enable) {
return keepAliveTime(DEFAULT_KEEPALIVE_TIME_NANOS, TimeUnit.NANOSECONDS);
}
return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
}
/**
* Enable keepalive with custom delay and timeout.
*
* @deprecated Please use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
*/
@Deprecated
public final NettyChannelBuilder enableKeepAlive(boolean enable, long keepAliveTime,
TimeUnit delayUnit, long keepAliveTimeout, TimeUnit timeoutUnit) {
if (enable) {
return keepAliveTime(keepAliveTime, delayUnit)
.keepAliveTimeout(keepAliveTimeout, timeoutUnit);
}
return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
}
/**
* Sets the time without read activity before sending a keepalive ping. An unreasonably small
* value might be increased, and {@code Long.MAX_VALUE} nano seconds or an unreasonably large
* value will disable keepalive. Defaults to infinite.
*
*
Clients must receive permission from the service owner before enabling this option.
* Keepalives can increase the load on services and are nl.topicus.jdbc.shaded.com.only "invisible" making it hard to
* notice when they are causing excessive load. Clients are strongly encouraged to use only as
* small of a value as necessary.
*
* @since 1.3.0
*/
public NettyChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
// Bump keepalive time to infinite. This disables keepalive.
keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
}
return this;
}
/**
* Sets the time waiting for read activity after sending a keepalive ping. If the time expires
* without any read activity on the connection, the connection is considered dead. An unreasonably
* small value might be increased. Defaults to 20 seconds.
*
*
This value should be at least multiple times the RTT to allow for lost packets.
*
* @since 1.3.0
*/
public NettyChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
return this;
}
/**
* Sets whether keepalive will be performed when there are no outstanding RPC on a connection.
* Defaults to {@code false}.
*
*
Clients must receive permission from the service owner before enabling this option.
* Keepalives on unused connections can easilly accidentally consume a considerable amount of
* bandwidth and CPU.
*
* @since 1.3.0
* @see #keepAliveTime(long, TimeUnit)
*/
public NettyChannelBuilder keepAliveWithoutCalls(boolean enable) {
keepAliveWithoutCalls = enable;
return this;
}
@Override
@CheckReturnValue
@Internal
protected ClientTransportFactory buildTransportFactory() {
return new NettyTransportFactory(dynamicParamsFactory, channelType, channelOptions,
negotiationType, sslContext, eventLoopGroup, flowControlWindow, maxInboundMessageSize(),
maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls);
}
@Override
@CheckReturnValue
protected Attributes getNameResolverParams() {
int defaultPort;
switch (negotiationType) {
case PLAINTEXT:
case PLAINTEXT_UPGRADE:
defaultPort = GrpcUtil.DEFAULT_PORT_PLAINTEXT;
break;
case TLS:
defaultPort = GrpcUtil.DEFAULT_PORT_SSL;
break;
default:
throw new AssertionError(negotiationType + " not handled");
}
return Attributes.newBuilder()
.set(NameResolver.Factory.PARAMS_DEFAULT_PORT, defaultPort).build();
}
void overrideAuthorityChecker(@Nullable OverrideAuthorityChecker authorityChecker) {
this.authorityChecker = authorityChecker;
}
@VisibleForTesting
@CheckReturnValue
static ProtocolNegotiator createProtocolNegotiator(
String authority,
NegotiationType negotiationType,
SslContext sslContext) {
ProtocolNegotiator negotiator =
createProtocolNegotiatorByType(authority, negotiationType, sslContext);
String proxy = System.getenv("GRPC_PROXY_EXP");
if (proxy != null) {
String[] parts = proxy.split(":", 2);
int port = 80;
if (parts.length > 1) {
port = Integer.parseInt(parts[1]);
}
InetSocketAddress proxyAddress = new InetSocketAddress(parts[0], port);
negotiator = ProtocolNegotiators.httpProxy(proxyAddress, null, null, negotiator);
}
return negotiator;
}
@CheckReturnValue
private static ProtocolNegotiator createProtocolNegotiatorByType(
String authority,
NegotiationType negotiationType,
SslContext sslContext) {
switch (negotiationType) {
case PLAINTEXT:
return ProtocolNegotiators.plaintext();
case PLAINTEXT_UPGRADE:
return ProtocolNegotiators.plaintextUpgrade();
case TLS:
return ProtocolNegotiators.tls(sslContext, authority);
default:
throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType);
}
}
@CheckReturnValue
interface OverrideAuthorityChecker {
String checkAuthority(String authority);
}
@Override
@CheckReturnValue
@Internal
protected String checkAuthority(String authority) {
if (authorityChecker != null) {
return authorityChecker.checkAuthority(authority);
}
return super.checkAuthority(authority);
}
void setDynamicParamsFactory(TransportCreationParamsFilterFactory factory) {
this.dynamicParamsFactory = checkNotNull(factory, "factory");
}
interface TransportCreationParamsFilterFactory {
@CheckReturnValue
TransportCreationParamsFilter create(
SocketAddress targetServerAddress, String authority, @Nullable String userAgent);
}
@CheckReturnValue
interface TransportCreationParamsFilter {
SocketAddress getTargetServerAddress();
String getAuthority();
@Nullable String getUserAgent();
ProtocolNegotiator getProtocolNegotiator();
}
/**
* Creates Netty transports. Exposed for internal use, as it should be private.
*/
@CheckReturnValue
private static final class NettyTransportFactory implements ClientTransportFactory {
private final TransportCreationParamsFilterFactory transportCreationParamsFilterFactory;
private final Class extends Channel> channelType;
private final Map, ?> channelOptions;
private final NegotiationType negotiationType;
private final SslContext sslContext;
private final EventLoopGroup group;
private final boolean usingSharedGroup;
private final int flowControlWindow;
private final int maxMessageSize;
private final int maxHeaderListSize;
private final AtomicBackoff keepAliveTimeNanos;
private final long keepAliveTimeoutNanos;
private final boolean keepAliveWithoutCalls;
private boolean closed;
NettyTransportFactory(TransportCreationParamsFilterFactory transportCreationParamsFilterFactory,
Class extends Channel> channelType, Map, ?> channelOptions,
NegotiationType negotiationType, SslContext sslContext, EventLoopGroup group,
int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls) {
this.channelType = channelType;
this.negotiationType = negotiationType;
this.channelOptions = new HashMap, Object>(channelOptions);
if (negotiationType == NegotiationType.TLS && sslContext == null) {
try {
sslContext = GrpcSslContexts.forClient().build();
} catch (SSLException ex) {
throw new RuntimeException(ex);
}
}
this.sslContext = sslContext;
if (transportCreationParamsFilterFactory == null) {
transportCreationParamsFilterFactory = new TransportCreationParamsFilterFactory() {
@Override
public TransportCreationParamsFilter create(
SocketAddress targetServerAddress, String authority, String userAgent) {
return new DynamicNettyTransportParams(targetServerAddress, authority, userAgent);
}
};
}
this.transportCreationParamsFilterFactory = transportCreationParamsFilterFactory;
this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize;
this.keepAliveTimeNanos = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
this.keepAliveWithoutCalls = keepAliveWithoutCalls;
usingSharedGroup = group == null;
if (usingSharedGroup) {
// The group was unspecified, using the shared group.
this.group = SharedResourceHolder.get(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP);
} else {
this.group = group;
}
}
@Override
public ConnectionClientTransport newClientTransport(
SocketAddress serverAddress, String authority, @Nullable String userAgent) {
checkState(!closed, "The transport factory is closed.");
TransportCreationParamsFilter dparams =
transportCreationParamsFilterFactory.create(serverAddress, authority, userAgent);
final AtomicBackoff.State keepAliveTimeNanosState = keepAliveTimeNanos.getState();
Runnable tooManyPingsRunnable = new Runnable() {
@Override
public void run() {
keepAliveTimeNanosState.backoff();
}
};
NettyClientTransport transport = new NettyClientTransport(
dparams.getTargetServerAddress(), channelType, channelOptions, group,
dparams.getProtocolNegotiator(), flowControlWindow,
maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
keepAliveWithoutCalls, dparams.getAuthority(), dparams.getUserAgent(),
tooManyPingsRunnable);
return transport;
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
if (usingSharedGroup) {
SharedResourceHolder.release(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP, group);
}
}
@CheckReturnValue
private final class DynamicNettyTransportParams implements TransportCreationParamsFilter {
private final SocketAddress targetServerAddress;
private final String authority;
@Nullable private final String userAgent;
private DynamicNettyTransportParams(
SocketAddress targetServerAddress, String authority, String userAgent) {
this.targetServerAddress = targetServerAddress;
this.authority = authority;
this.userAgent = userAgent;
}
@Override
public SocketAddress getTargetServerAddress() {
return targetServerAddress;
}
@Override
public String getAuthority() {
return authority;
}
@Override
public String getUserAgent() {
return userAgent;
}
@Override
public ProtocolNegotiator getProtocolNegotiator() {
return createProtocolNegotiator(authority, negotiationType, sslContext);
}
}
}
}