nl.topicus.jdbc.shaded.io.grpc.netty.NettyChannelBuilder Maven / Gradle / Ivy
Show all versions of spanner-jdbc Show documentation
/*
* Copyright 2014, gRPC Authors 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 nl.topicus.jdbc.shaded.io.grpc.netty;
import static nl.topicus.jdbc.shaded.com.google.common.base.Preconditions.checkArgument;
import static nl.topicus.jdbc.shaded.com.google.common.base.Preconditions.checkNotNull;
import static nl.topicus.jdbc.shaded.com.google.common.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.common.annotations.VisibleForTesting;
import nl.topicus.jdbc.shaded.com.google.common.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.ProxyParameters;
import nl.topicus.jdbc.shaded.io.grpc.internal.SharedResourceHolder;
import nl.topicus.jdbc.shaded.io.grpc.internal.TransportTracer;
import nl.topicus.jdbc.shaded.io.netty.channel.Channel;
import nl.topicus.jdbc.shaded.io.netty.channel.ChannelOption;
import nl.topicus.jdbc.shaded.io.netty.channel.EventLoopGroup;
import nl.topicus.jdbc.shaded.io.netty.channel.socket.nio.NioSocketChannel;
import nl.topicus.jdbc.shaded.io.netty.handler.ssl.SslContext;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import nl.topicus.jdbc.shaded.javax.annotation.CheckReturnValue;
import nl.topicus.jdbc.shaded.javax.annotation.Nullable;
import javax.net.ssl.SSLException;
/**
* A builder to help simplify construction of channels using the Netty transport.
*/
@ExperimentalApi("https://github.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 network 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 netty 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. This is cumulative size of the
* headers with some overhead, as defined for
*
* HTTP/2's SETTINGS_MAX_HEADER_LIST_SIZE. The default is 8 KiB.
*/
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;
}
/**
* Equivalent to using {@link #negotiationType(NegotiationType)} with {@code TLS}.
*/
@Override
public NettyChannelBuilder useTransportSecurity() {
negotiationType(NegotiationType.TLS);
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);
}
/**
* {@inheritDoc}
*
* @since 1.3.0
*/
@Override
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;
}
/**
* {@inheritDoc}
*
* @since 1.3.0
*/
@Override
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;
}
/**
* {@inheritDoc}
*
* @since 1.3.0
*/
@Override
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,
transportTracerFactory.create());
}
@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,
ProxyParameters proxy) {
ProtocolNegotiator negotiator =
createProtocolNegotiatorByType(authority, negotiationType, sslContext);
if (proxy != null) {
negotiator = ProtocolNegotiators.httpProxy(
proxy.proxyAddress, proxy.username, proxy.password, 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");
}
@Override
protected void setTracingEnabled(boolean value) {
super.setTracingEnabled(value);
}
@Override
protected void setStatsEnabled(boolean value) {
super.setStatsEnabled(value);
}
@Override
protected void setStatsRecordStartedRpcs(boolean value) {
super.setStatsRecordStartedRpcs(value);
}
@VisibleForTesting
NettyChannelBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) {
this.transportTracerFactory = transportTracerFactory;
return this;
}
interface TransportCreationParamsFilterFactory {
@CheckReturnValue
TransportCreationParamsFilter create(
SocketAddress targetServerAddress,
String authority,
@Nullable String userAgent,
@Nullable ProxyParameters proxy);
}
@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 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 final TransportTracer transportTracer;
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,
TransportTracer transportTracer) {
this.channelType = channelType;
this.negotiationType = negotiationType;
this.channelOptions = new HashMap, Object>(channelOptions);
this.transportTracer = transportTracer;
if (transportCreationParamsFilterFactory == null) {
transportCreationParamsFilterFactory =
new DefaultNettyTransportCreationParamsFilterFactory(sslContext);
}
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,
@Nullable ProxyParameters proxy) {
checkState(!closed, "The transport factory is closed.");
TransportCreationParamsFilter dparams =
transportCreationParamsFilterFactory.create(serverAddress, authority, userAgent, proxy);
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, transportTracer);
return transport;
}
@Override
public ScheduledExecutorService getScheduledExecutorService() {
return group;
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
if (usingSharedGroup) {
SharedResourceHolder.release(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP, group);
}
}
private final class DefaultNettyTransportCreationParamsFilterFactory
implements TransportCreationParamsFilterFactory {
private final SslContext sslContext;
private DefaultNettyTransportCreationParamsFilterFactory(SslContext sslContext) {
if (negotiationType == NegotiationType.TLS && sslContext == null) {
try {
sslContext = GrpcSslContexts.forClient().build();
} catch (SSLException ex) {
throw new RuntimeException(ex);
}
}
this.sslContext = sslContext;
}
@Override
public TransportCreationParamsFilter create(
SocketAddress targetServerAddress,
String authority,
String userAgent,
ProxyParameters proxyParams) {
return new DynamicNettyTransportParams(
targetServerAddress, authority, userAgent, proxyParams);
}
@CheckReturnValue
private final class DynamicNettyTransportParams implements TransportCreationParamsFilter {
private final SocketAddress targetServerAddress;
private final String authority;
@Nullable private final String userAgent;
private ProxyParameters proxyParams;
private DynamicNettyTransportParams(
SocketAddress targetServerAddress,
String authority,
String userAgent,
ProxyParameters proxyParams) {
this.targetServerAddress = targetServerAddress;
this.authority = authority;
this.userAgent = userAgent;
this.proxyParams = proxyParams;
}
@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, proxyParams);
}
}
}
}
}