Add overrideAuthority to NettyChannelBuilder

Although the functionality is currently available by passing a
manually-created InetAddress, that requires that the user do I/O before
calling our API and does not work with naming in the future.
This commit is contained in:
Eric Anderson 2015-09-04 09:29:34 -07:00
parent a6621daca2
commit 3ae18eaef1
4 changed files with 90 additions and 26 deletions

View File

@ -43,6 +43,8 @@ import io.grpc.Status;
import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.internal.SharedResourceHolder.Resource;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -295,6 +297,46 @@ public final class GrpcUtil {
return builder.toString(); return builder.toString();
} }
/**
* Parse an authority into a URI for retrieving the host and port.
*/
public static URI authorityToUri(String authority) {
Preconditions.checkNotNull(authority, "authority");
URI uri;
try {
uri = new URI(null, authority, null, null, null);
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Invalid authority: " + authority, ex);
}
if (uri.getUserInfo() != null) {
throw new IllegalArgumentException(
"Userinfo must not be present on authority: " + authority);
}
return uri;
}
/**
* Verify {@code authority} is valid for use with gRPC. The syntax must be valid and it must not
* include userinfo.
*
* @return the {@code authority} provided
*/
public static String checkAuthority(String authority) {
authorityToUri(authority);
return authority;
}
/**
* Combine a host and port into an authority string.
*/
public static String authorityFromHostAndPort(String host, int port) {
try {
return new URI(null, null, host, port, null, null, null).getAuthority();
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
}
}
/** /**
* Shared executor for channels. * Shared executor for channels.
*/ */

View File

@ -41,6 +41,7 @@ import io.grpc.internal.AbstractManagedChannelImplBuilder;
import io.grpc.internal.AbstractReferenceCounted; import io.grpc.internal.AbstractReferenceCounted;
import io.grpc.internal.ClientTransport; import io.grpc.internal.ClientTransport;
import io.grpc.internal.ClientTransportFactory; import io.grpc.internal.ClientTransportFactory;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.SharedResourceHolder;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
@ -62,6 +63,7 @@ public final class NettyChannelBuilder
public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB
private final SocketAddress serverAddress; private final SocketAddress serverAddress;
private String authority;
private NegotiationType negotiationType = NegotiationType.TLS; private NegotiationType negotiationType = NegotiationType.TLS;
private Class<? extends Channel> channelType = NioSocketChannel.class; private Class<? extends Channel> channelType = NioSocketChannel.class;
@Nullable @Nullable
@ -71,21 +73,36 @@ public final class NettyChannelBuilder
private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE; private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
/** /**
* Creates a new builder with the given server address. * 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.
*/ */
public static NettyChannelBuilder forAddress(SocketAddress serverAddress) { public static NettyChannelBuilder forAddress(SocketAddress serverAddress) {
return new NettyChannelBuilder(serverAddress); String authority;
if (serverAddress instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) serverAddress;
authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
} else {
// Specialized address types are allowed to support custom Channel types so just assume their
// toString() values are valid :authority values. We defer checking validity of authority
// until buildTransportFactory() to provide the user an opportunity to override the value.
authority = serverAddress.toString();
}
return new NettyChannelBuilder(serverAddress, authority);
} }
/** /**
* Creates a new builder with the given host and port. * Creates a new builder with the given host and port.
*/ */
public static NettyChannelBuilder forAddress(String host, int port) { public static NettyChannelBuilder forAddress(String host, int port) {
return forAddress(new InetSocketAddress(host, port)); return new NettyChannelBuilder(
new InetSocketAddress(host, port), GrpcUtil.authorityFromHostAndPort(host, port));
} }
private NettyChannelBuilder(SocketAddress serverAddress) { private NettyChannelBuilder(SocketAddress serverAddress, String authority) {
this.serverAddress = serverAddress; this.serverAddress = serverAddress;
this.authority = authority;
} }
/** /**
@ -149,10 +166,23 @@ public final class NettyChannelBuilder
return this; return this;
} }
/**
* Overrides the authority used with TLS and HTTP virtual hosting. It does not change what host is
* actually connected to. Is commonly in the form {@code host:port}.
*
* <p>Should only used by tests.
*/
public NettyChannelBuilder overrideAuthority(String authority) {
this.authority = GrpcUtil.checkAuthority(authority);
return this;
}
@Override @Override
protected ClientTransportFactory buildTransportFactory() { protected ClientTransportFactory buildTransportFactory() {
return new NettyTransportFactory(serverAddress, channelType, eventLoopGroup, flowControlWindow, // Check authority, since non-inet ServerAddresses delay the authority check.
createProtocolNegotiator(), maxMessageSize); GrpcUtil.checkAuthority(authority);
return new NettyTransportFactory(serverAddress, authority, channelType, eventLoopGroup,
flowControlWindow, createProtocolNegotiator(), maxMessageSize);
} }
private ProtocolNegotiator createProtocolNegotiator() { private ProtocolNegotiator createProtocolNegotiator() {
@ -163,9 +193,6 @@ public final class NettyChannelBuilder
case PLAINTEXT_UPGRADE: case PLAINTEXT_UPGRADE:
return ProtocolNegotiators.plaintextUpgrade(); return ProtocolNegotiators.plaintextUpgrade();
case TLS: case TLS:
if (!(serverAddress instanceof InetSocketAddress)) {
throw new IllegalStateException("TLS not supported for non-internet socket types");
}
if (sslContext == null) { if (sslContext == null) {
try { try {
sslContext = GrpcSslContexts.forClient().build(); sslContext = GrpcSslContexts.forClient().build();
@ -173,7 +200,7 @@ public final class NettyChannelBuilder
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
return ProtocolNegotiators.tls(sslContext, (InetSocketAddress) serverAddress); return ProtocolNegotiators.tls(sslContext, authority);
default: default:
throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType); throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType);
} }
@ -191,6 +218,7 @@ public final class NettyChannelBuilder
private final String authority; private final String authority;
private NettyTransportFactory(SocketAddress serverAddress, private NettyTransportFactory(SocketAddress serverAddress,
String authority,
Class<? extends Channel> channelType, Class<? extends Channel> channelType,
EventLoopGroup group, EventLoopGroup group,
int flowControlWindow, int flowControlWindow,
@ -201,14 +229,7 @@ public final class NettyChannelBuilder
this.flowControlWindow = flowControlWindow; this.flowControlWindow = flowControlWindow;
this.negotiator = negotiator; this.negotiator = negotiator;
this.maxMessageSize = maxMessageSize; this.maxMessageSize = maxMessageSize;
if (serverAddress instanceof InetSocketAddress) { this.authority = authority;
InetSocketAddress address = (InetSocketAddress) serverAddress;
this.authority = address.getHostString() + ":" + address.getPort();
} else {
// Specialized address types are allowed to support custom Channel types so just assume
// their toString() values are valid :authority values
this.authority = serverAddress.toString();
}
usingSharedGroup = group == null; usingSharedGroup = group == null;
if (usingSharedGroup) { if (usingSharedGroup) {

View File

@ -34,6 +34,7 @@ package io.grpc.netty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.internal.GrpcUtil;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerAdapter;
@ -54,7 +55,7 @@ import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.util.ByteString; import io.netty.util.ByteString;
import java.net.InetSocketAddress; import java.net.URI;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Arrays; import java.util.Arrays;
import java.util.Queue; import java.util.Queue;
@ -130,9 +131,9 @@ public final class ProtocolNegotiators {
* may happen immediately, even before the TLS Handshake is complete. * may happen immediately, even before the TLS Handshake is complete.
*/ */
public static ProtocolNegotiator tls(final SslContext sslContext, public static ProtocolNegotiator tls(final SslContext sslContext,
final InetSocketAddress inetAddress) { String authority) {
Preconditions.checkNotNull(sslContext, "sslContext"); Preconditions.checkNotNull(sslContext, "sslContext");
Preconditions.checkNotNull(inetAddress, "inetAddress"); final URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority"));
return new ProtocolNegotiator() { return new ProtocolNegotiator() {
@Override @Override
@ -140,8 +141,7 @@ public final class ProtocolNegotiators {
ChannelHandler sslBootstrap = new ChannelHandlerAdapter() { ChannelHandler sslBootstrap = new ChannelHandlerAdapter() {
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), uri.getHost(), uri.getPort());
inetAddress.getHostName(), inetAddress.getPort());
SSLParameters sslParams = new SSLParameters(); SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslParams.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParams); sslEngine.setSSLParameters(sslParams);

View File

@ -96,6 +96,7 @@ public class NettyClientTransportTest {
private final List<NettyClientTransport> transports = new ArrayList<NettyClientTransport>(); private final List<NettyClientTransport> transports = new ArrayList<NettyClientTransport>();
private NioEventLoopGroup group; private NioEventLoopGroup group;
private InetSocketAddress address; private InetSocketAddress address;
private String authority;
private NettyServer server; private NettyServer server;
private EchoServerListener serverListener = new EchoServerListener(); private EchoServerListener serverListener = new EchoServerListener();
@ -105,6 +106,7 @@ public class NettyClientTransportTest {
group = new NioEventLoopGroup(1); group = new NioEventLoopGroup(1);
address = TestUtils.testServerAddress(TestUtils.pickUnusedPort()); address = TestUtils.testServerAddress(TestUtils.pickUnusedPort());
authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
} }
@After @After
@ -236,7 +238,7 @@ public class NettyClientTransportTest {
File clientCert = TestUtils.loadCert("ca.pem"); File clientCert = TestUtils.loadCert("ca.pem");
SslContext clientContext = GrpcSslContexts.forClient().trustManager(clientCert) SslContext clientContext = GrpcSslContexts.forClient().trustManager(clientCert)
.ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build(); .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build();
return ProtocolNegotiators.tls(clientContext, address); return ProtocolNegotiators.tls(clientContext, authority);
} }
private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { private NettyClientTransport newTransport(ProtocolNegotiator negotiator) {
@ -245,8 +247,7 @@ public class NettyClientTransportTest {
private NettyClientTransport newTransport(ProtocolNegotiator negotiator, int maxMsgSize) { private NettyClientTransport newTransport(ProtocolNegotiator negotiator, int maxMsgSize) {
NettyClientTransport transport = new NettyClientTransport(address, NioSocketChannel.class, NettyClientTransport transport = new NettyClientTransport(address, NioSocketChannel.class,
group, negotiator, DEFAULT_WINDOW_SIZE, maxMsgSize, group, negotiator, DEFAULT_WINDOW_SIZE, maxMsgSize, authority);
address.getHostString() + ":" + address.getPort());
transports.add(transport); transports.add(transport);
return transport; return transport;
} }