Skip to content

Commit d9320ee

Browse files
committed
netty: Add RFC 3986 support to the 'unix:' name resolver.
1 parent d5536b3 commit d9320ee

File tree

4 files changed

+74
-12
lines changed

4 files changed

+74
-12
lines changed

netty/src/main/java/io/grpc/netty/UdsNameResolver.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21+
import static com.google.common.base.Strings.isNullOrEmpty;
2122

2223
import com.google.common.base.Preconditions;
2324
import io.grpc.EquivalentAddressGroup;
@@ -31,8 +32,18 @@ final class UdsNameResolver extends NameResolver {
3132
private NameResolver.Listener2 listener;
3233
private final String authority;
3334

35+
/**
36+
* Constructs a new instance of UdsNameResolver.
37+
*
38+
* @param authority authority of the 'unix:' URI to resolve, or null if target has no authority
39+
* @param targetPath path of the 'unix:' URI to resolve
40+
*/
3441
UdsNameResolver(String authority, String targetPath, Args args) {
35-
checkArgument(authority == null, "non-null authority not supported");
42+
// UDS is inherently local. According to https://github.com/grpc/grpc/blob/master/doc/naming.md,
43+
// this is expressed in the target URI either by using a blank authority, like "unix:///sock",
44+
// or by omitting authority completely, e.g. "unix:/sock".
45+
// TODO(jdcormie): Allow the explicit authority string "localhost"?
46+
checkArgument(isNullOrEmpty(authority), "authority not supported: %s", authority);
3647
this.authority = targetPath;
3748
}
3849

netty/src/main/java/io/grpc/netty/UdsNameResolverProvider.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.grpc.Internal;
2121
import io.grpc.NameResolver;
2222
import io.grpc.NameResolverProvider;
23+
import io.grpc.Uri;
2324
import io.netty.channel.unix.DomainSocketAddress;
2425
import java.net.SocketAddress;
2526
import java.net.URI;
@@ -31,9 +32,21 @@ public final class UdsNameResolverProvider extends NameResolverProvider {
3132

3233
private static final String SCHEME = "unix";
3334

35+
@Override
36+
public NameResolver newNameResolver(Uri targetUri, NameResolver.Args args) {
37+
if (SCHEME.equals(targetUri.getScheme())) {
38+
return new UdsNameResolver(targetUri.getAuthority(), targetUri.getPath(), args);
39+
} else {
40+
return null;
41+
}
42+
}
43+
3444
@Override
3545
public UdsNameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
3646
if (SCHEME.equals(targetUri.getScheme())) {
47+
// TODO(jdcormie): java.net.URI has a bug where getAuthority() returns null for both the
48+
// undefined and zero-length authority. Doesn't matter for now because UdsNameResolver doesn't
49+
// distinguish these cases.
3750
return new UdsNameResolver(targetUri.getAuthority(), getTargetPathFromUri(targetUri), args);
3851
} else {
3952
return null;
@@ -44,6 +57,10 @@ static String getTargetPathFromUri(URI targetUri) {
4457
Preconditions.checkArgument(SCHEME.equals(targetUri.getScheme()), "scheme must be " + SCHEME);
4558
String targetPath = targetUri.getPath();
4659
if (targetPath == null) {
60+
// TODO(jdcormie): This incorrectly includes '?' and any characters that follow. In the
61+
// hierarchical case ('unix:///path'), java.net.URI parses these into a query component that's
62+
// distinct from the path. But in the present "opaque" case ('unix:/path'), what may look like
63+
// a query is considered part of the SSP.
4764
targetPath = Preconditions.checkNotNull(targetUri.getSchemeSpecificPart(), "targetPath");
4865
}
4966
return targetPath;

netty/src/test/java/io/grpc/netty/UdsNameResolverProviderTest.java

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.grpc.netty;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.common.truth.TruthJUnit.assume;
2021
import static org.junit.Assert.fail;
2122
import static org.mockito.Mockito.mock;
2223
import static org.mockito.Mockito.verify;
@@ -26,26 +27,38 @@
2627
import io.grpc.NameResolver;
2728
import io.grpc.NameResolver.ServiceConfigParser;
2829
import io.grpc.SynchronizationContext;
30+
import io.grpc.Uri;
2931
import io.grpc.internal.FakeClock;
3032
import io.grpc.internal.GrpcUtil;
3133
import io.netty.channel.unix.DomainSocketAddress;
3234
import java.net.SocketAddress;
3335
import java.net.URI;
36+
import java.util.Arrays;
3437
import java.util.List;
3538
import org.junit.Rule;
3639
import org.junit.Test;
3740
import org.junit.runner.RunWith;
38-
import org.junit.runners.JUnit4;
41+
import org.junit.runners.Parameterized;
42+
import org.junit.runners.Parameterized.Parameter;
43+
import org.junit.runners.Parameterized.Parameters;
3944
import org.mockito.ArgumentCaptor;
4045
import org.mockito.Captor;
4146
import org.mockito.Mock;
4247
import org.mockito.junit.MockitoJUnit;
4348
import org.mockito.junit.MockitoRule;
4449

4550
/** Unit tests for {@link UdsNameResolverProvider}. */
46-
@RunWith(JUnit4.class)
51+
@RunWith(Parameterized.class)
4752
public class UdsNameResolverProviderTest {
4853
private static final int DEFAULT_PORT = 887;
54+
55+
@Parameters(name = "enableRfc3986UrisParam={0}")
56+
public static Iterable<Object[]> data() {
57+
return Arrays.asList(new Object[][] {{true}, {false}});
58+
}
59+
60+
@Parameter public boolean enableRfc3986UrisParam;
61+
4962
@Rule
5063
public final MockitoRule mocks = MockitoJUnit.rule();
5164

@@ -73,38 +86,59 @@ public class UdsNameResolverProviderTest {
7386

7487
@Test
7588
public void testUnixRelativePath() {
76-
UdsNameResolver udsNameResolver =
77-
udsNameResolverProvider.newNameResolver(URI.create("unix:sock.sock"), args);
89+
UdsNameResolver udsNameResolver = newNameResolver("unix:sock.sock", args);
7890
DomainSocketAddress domainSocketAddress = startAndGetUniqueResolvedAddress(udsNameResolver);
7991
assertThat(domainSocketAddress.path()).isEqualTo("sock.sock");
8092
}
8193

8294
@Test
8395
public void testUnixAbsolutePath() {
84-
UdsNameResolver udsNameResolver =
85-
udsNameResolverProvider.newNameResolver(URI.create("unix:/sock.sock"), args);
96+
UdsNameResolver udsNameResolver = newNameResolver("unix:/sock.sock", args);
8697
DomainSocketAddress domainSocketAddress = startAndGetUniqueResolvedAddress(udsNameResolver);
8798
assertThat(domainSocketAddress.path()).isEqualTo("/sock.sock");
8899
}
89100

90101
@Test
91102
public void testUnixAbsoluteAlternatePath() {
92-
UdsNameResolver udsNameResolver =
93-
udsNameResolverProvider.newNameResolver(URI.create("unix:///sock.sock"), args);
103+
UdsNameResolver udsNameResolver = newNameResolver("unix:///sock.sock", args);
94104
DomainSocketAddress domainSocketAddress = startAndGetUniqueResolvedAddress(udsNameResolver);
95105
assertThat(domainSocketAddress.path()).isEqualTo("/sock.sock");
96106
}
97107

98108
@Test
99109
public void testUnixPathWithAuthority() {
100110
try {
101-
udsNameResolverProvider.newNameResolver(URI.create("unix://localhost/sock.sock"), args);
111+
newNameResolver("unix://localhost/sock.sock", args);
102112
fail("exception expected");
103113
} catch (IllegalArgumentException e) {
104114
assertThat(e).hasMessageThat().isEqualTo("authority not supported: localhost");
105115
}
106116
}
107117

118+
@Test
119+
public void testUnixAbsolutePathDoesNotIncludeQueryOrFragment() {
120+
UdsNameResolver udsNameResolver = newNameResolver("unix:///sock.sock?query#fragment", args);
121+
DomainSocketAddress domainSocketAddress = startAndGetUniqueResolvedAddress(udsNameResolver);
122+
assertThat(domainSocketAddress.path()).isEqualTo("/sock.sock");
123+
}
124+
125+
@Test
126+
public void testUnixRelativePathDoesNotIncludeQueryOrFragment() {
127+
// This test fails without RFC 3986 support because of a bug in the legacy java.net.URI-based
128+
// NRP implementation.
129+
assume().that(enableRfc3986UrisParam).isTrue();
130+
131+
UdsNameResolver udsNameResolver = newNameResolver("unix:sock.sock?query#fragment", args);
132+
DomainSocketAddress domainSocketAddress = startAndGetUniqueResolvedAddress(udsNameResolver);
133+
assertThat(domainSocketAddress.path()).isEqualTo("sock.sock");
134+
}
135+
136+
private UdsNameResolver newNameResolver(String uriString, NameResolver.Args args) {
137+
return enableRfc3986UrisParam
138+
? (UdsNameResolver) udsNameResolverProvider.newNameResolver(Uri.create(uriString), args)
139+
: udsNameResolverProvider.newNameResolver(URI.create(uriString), args);
140+
}
141+
108142
private DomainSocketAddress startAndGetUniqueResolvedAddress(UdsNameResolver udsNameResolver) {
109143
assertThat(udsNameResolver).isNotNull();
110144
udsNameResolver.start(mockListener);

netty/src/test/java/io/grpc/netty/UdsNameResolverTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ public void testValidTargetPath() {
9191
@Test
9292
public void testNonNullAuthority() {
9393
try {
94-
udsNameResolver = new UdsNameResolver("authority", "sock.sock", args);
94+
udsNameResolver = new UdsNameResolver("somehost", "sock.sock", args);
9595
fail("exception expected");
9696
} catch (IllegalArgumentException e) {
97-
assertThat(e).hasMessageThat().isEqualTo("non-null authority not supported");
97+
assertThat(e).hasMessageThat().isEqualTo("authority not supported: somehost");
9898
}
9999
}
100100
}

0 commit comments

Comments
 (0)