From 3ae18eaef1f93c80d6319fc1b897341388677f4d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 4 Sep 2015 09:29:34 -0700 Subject: [PATCH] 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. --- .../main/java/io/grpc/internal/GrpcUtil.java | 42 ++++++++++++++ .../io/grpc/netty/NettyChannelBuilder.java | 57 +++++++++++++------ .../io/grpc/netty/ProtocolNegotiators.java | 10 ++-- .../grpc/netty/NettyClientTransportTest.java | 7 ++- 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index d258ffce33..5d0e205f1b 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -43,6 +43,8 @@ import io.grpc.Status; import io.grpc.internal.SharedResourceHolder.Resource; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -295,6 +297,46 @@ public final class GrpcUtil { 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. */ diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java index 2e97c2a690..c4bd2d07c3 100644 --- a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java @@ -41,6 +41,7 @@ import io.grpc.internal.AbstractManagedChannelImplBuilder; import io.grpc.internal.AbstractReferenceCounted; import io.grpc.internal.ClientTransport; import io.grpc.internal.ClientTransportFactory; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.SharedResourceHolder; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; @@ -62,6 +63,7 @@ public final class NettyChannelBuilder public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB private final SocketAddress serverAddress; + private String authority; private NegotiationType negotiationType = NegotiationType.TLS; private Class channelType = NioSocketChannel.class; @Nullable @@ -71,21 +73,36 @@ public final class NettyChannelBuilder 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) { - 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. */ 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.authority = authority; } /** @@ -149,10 +166,23 @@ public final class NettyChannelBuilder 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}. + * + *

Should only used by tests. + */ + public NettyChannelBuilder overrideAuthority(String authority) { + this.authority = GrpcUtil.checkAuthority(authority); + return this; + } + @Override protected ClientTransportFactory buildTransportFactory() { - return new NettyTransportFactory(serverAddress, channelType, eventLoopGroup, flowControlWindow, - createProtocolNegotiator(), maxMessageSize); + // Check authority, since non-inet ServerAddresses delay the authority check. + GrpcUtil.checkAuthority(authority); + return new NettyTransportFactory(serverAddress, authority, channelType, eventLoopGroup, + flowControlWindow, createProtocolNegotiator(), maxMessageSize); } private ProtocolNegotiator createProtocolNegotiator() { @@ -163,9 +193,6 @@ public final class NettyChannelBuilder case PLAINTEXT_UPGRADE: return ProtocolNegotiators.plaintextUpgrade(); case TLS: - if (!(serverAddress instanceof InetSocketAddress)) { - throw new IllegalStateException("TLS not supported for non-internet socket types"); - } if (sslContext == null) { try { sslContext = GrpcSslContexts.forClient().build(); @@ -173,7 +200,7 @@ public final class NettyChannelBuilder throw new RuntimeException(ex); } } - return ProtocolNegotiators.tls(sslContext, (InetSocketAddress) serverAddress); + return ProtocolNegotiators.tls(sslContext, authority); default: throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType); } @@ -191,6 +218,7 @@ public final class NettyChannelBuilder private final String authority; private NettyTransportFactory(SocketAddress serverAddress, + String authority, Class channelType, EventLoopGroup group, int flowControlWindow, @@ -201,14 +229,7 @@ public final class NettyChannelBuilder this.flowControlWindow = flowControlWindow; this.negotiator = negotiator; this.maxMessageSize = maxMessageSize; - if (serverAddress instanceof InetSocketAddress) { - 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(); - } + this.authority = authority; usingSharedGroup = group == null; if (usingSharedGroup) { diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index ff3905faf3..e91b7fa4be 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -34,6 +34,7 @@ package io.grpc.netty; import com.google.common.base.Preconditions; import io.grpc.Status; +import io.grpc.internal.GrpcUtil; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; @@ -54,7 +55,7 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.ByteString; -import java.net.InetSocketAddress; +import java.net.URI; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Queue; @@ -130,9 +131,9 @@ public final class ProtocolNegotiators { * may happen immediately, even before the TLS Handshake is complete. */ public static ProtocolNegotiator tls(final SslContext sslContext, - final InetSocketAddress inetAddress) { + String authority) { Preconditions.checkNotNull(sslContext, "sslContext"); - Preconditions.checkNotNull(inetAddress, "inetAddress"); + final URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority")); return new ProtocolNegotiator() { @Override @@ -140,8 +141,7 @@ public final class ProtocolNegotiators { ChannelHandler sslBootstrap = new ChannelHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), - inetAddress.getHostName(), inetAddress.getPort()); + SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), uri.getHost(), uri.getPort()); SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslEngine.setSSLParameters(sslParams); diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index b0479e1606..83f634b5f9 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -96,6 +96,7 @@ public class NettyClientTransportTest { private final List transports = new ArrayList(); private NioEventLoopGroup group; private InetSocketAddress address; + private String authority; private NettyServer server; private EchoServerListener serverListener = new EchoServerListener(); @@ -105,6 +106,7 @@ public class NettyClientTransportTest { group = new NioEventLoopGroup(1); address = TestUtils.testServerAddress(TestUtils.pickUnusedPort()); + authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort()); } @After @@ -236,7 +238,7 @@ public class NettyClientTransportTest { File clientCert = TestUtils.loadCert("ca.pem"); SslContext clientContext = GrpcSslContexts.forClient().trustManager(clientCert) .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build(); - return ProtocolNegotiators.tls(clientContext, address); + return ProtocolNegotiators.tls(clientContext, authority); } private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { @@ -245,8 +247,7 @@ public class NettyClientTransportTest { private NettyClientTransport newTransport(ProtocolNegotiator negotiator, int maxMsgSize) { NettyClientTransport transport = new NettyClientTransport(address, NioSocketChannel.class, - group, negotiator, DEFAULT_WINDOW_SIZE, maxMsgSize, - address.getHostString() + ":" + address.getPort()); + group, negotiator, DEFAULT_WINDOW_SIZE, maxMsgSize, authority); transports.add(transport); return transport; }