diff --git a/api/src/main/java/io/grpc/Uri.java b/api/src/main/java/io/grpc/Uri.java index 0ef6212d35a..3034211752b 100644 --- a/api/src/main/java/io/grpc/Uri.java +++ b/api/src/main/java/io/grpc/Uri.java @@ -481,6 +481,15 @@ public String getPath() { *
Prefer this method over {@link #getPath()} because it preserves the distinction between * segment separators and literal '/'s within a path segment. * + *
A trailing '/' delimiter in the path results in the empty string as the last element in the
+ * returned list. For example, file://localhost/foo/bar/ has path segments
+ * ["foo", "bar", ""]
+ *
+ *
A leading '/' delimiter cannot be detected using this method. For example, both
+ * dns:example.com and dns:///example.com have the same list of path segments:
+ * ["example.com"]. Use {@link #isPathAbsolute()} or {@link #isPathRootless()} to
+ * distinguish these cases.
+ *
*
The returned list is immutable.
*/
public List The path of an RFC 3986 URI is either empty, absolute (starts with the '/' segment
+ * delimiter) or rootless (starts with a path segment). For example, Contrast rootless paths with absolute ones (see {@link #isPathAbsolute()}.
+ */
+ public boolean isPathRootless() {
+ return !path.isEmpty() && !path.startsWith("/");
+ }
+
+ /**
+ * Returns true iff this URI's path component starts with the '/' segment delimiter (rather than a
+ * path segment).
+ *
+ * The path of an RFC 3986 URI is either empty, absolute (starts with the '/' segment
+ * delimiter) or rootless (starts with a path segment). For example, Contrast absolute paths with rootless ones (see {@link #isPathRootless()}.
+ *
+ * NB: The term "absolute" has two different meanings in RFC 3986 which are easily confused.
+ * This method tests for a property of this URI's path component. Contrast with {@link
+ * #isAbsolute()} which tests the URI itself for a different property.
+ */
+ public boolean isPathAbsolute() {
+ return path.startsWith("/");
+ }
+
/**
* Returns the path component of this URI in its originally parsed, possibly percent-encoded form.
*/
diff --git a/api/src/test/java/io/grpc/UriTest.java b/api/src/test/java/io/grpc/UriTest.java
index 12fc9813b60..e34319e8910 100644
--- a/api/src/test/java/io/grpc/UriTest.java
+++ b/api/src/test/java/io/grpc/UriTest.java
@@ -46,6 +46,8 @@ public void parse_allComponents() throws URISyntaxException {
assertThat(uri.getFragment()).isEqualTo("fragment");
assertThat(uri.toString()).isEqualTo("scheme://user@host:0443/path?query#fragment");
assertThat(uri.isAbsolute()).isFalse(); // Has a fragment.
+ assertThat(uri.isPathAbsolute()).isTrue();
+ assertThat(uri.isPathRootless()).isFalse();
}
@Test
@@ -127,6 +129,8 @@ public void parse_emptyPathWithAuthority() throws URISyntaxException {
assertThat(uri.getFragment()).isNull();
assertThat(uri.toString()).isEqualTo("scheme://authority");
assertThat(uri.isAbsolute()).isTrue();
+ assertThat(uri.isPathAbsolute()).isFalse();
+ assertThat(uri.isPathRootless()).isFalse();
}
@Test
@@ -139,6 +143,8 @@ public void parse_rootless() throws URISyntaxException {
assertThat(uri.getFragment()).isNull();
assertThat(uri.toString()).isEqualTo("mailto:ceo@company.com?subject=raise");
assertThat(uri.isAbsolute()).isTrue();
+ assertThat(uri.isPathAbsolute()).isFalse();
+ assertThat(uri.isPathRootless()).isTrue();
}
@Test
@@ -151,6 +157,8 @@ public void parse_emptyPath() throws URISyntaxException {
assertThat(uri.getFragment()).isNull();
assertThat(uri.toString()).isEqualTo("scheme:");
assertThat(uri.isAbsolute()).isTrue();
+ assertThat(uri.isPathAbsolute()).isFalse();
+ assertThat(uri.isPathRootless()).isFalse();
}
@Test
@@ -348,12 +356,34 @@ public void parse_onePathSegment_trailingSlash() throws URISyntaxException {
assertThat(uri.getPathSegments()).containsExactly("foo", "");
}
+ @Test
+ public void parse_onePathSegment_rootless() throws URISyntaxException {
+ Uri uri = Uri.create("dns:www.example.com");
+ assertThat(uri.getPathSegments()).containsExactly("www.example.com");
+ assertThat(uri.isPathAbsolute()).isFalse();
+ assertThat(uri.isPathRootless()).isTrue();
+ }
+
@Test
public void parse_twoPathSegments() throws URISyntaxException {
Uri uri = Uri.create("file:/foo/bar");
assertThat(uri.getPathSegments()).containsExactly("foo", "bar");
}
+ @Test
+ public void parse_twoPathSegments_rootless() throws URISyntaxException {
+ Uri uri = Uri.create("file:foo/bar");
+ assertThat(uri.getPathSegments()).containsExactly("foo", "bar");
+ }
+
+ @Test
+ public void parse_percentEncodedPathSegment_rootless() throws URISyntaxException {
+ Uri uri = Uri.create("mailto:%2Fdev%2Fnull@example.com");
+ assertThat(uri.getPathSegments()).containsExactly("/dev/null@example.com");
+ assertThat(uri.isPathAbsolute()).isFalse();
+ assertThat(uri.isPathRootless()).isTrue();
+ }
+
@Test
public void toString_percentEncoding() throws URISyntaxException {
Uri uri =
diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
index c977fbb0cca..16edf767901 100644
--- a/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
@@ -21,25 +21,30 @@
import io.grpc.InternalServiceProviders;
import io.grpc.NameResolver;
import io.grpc.NameResolverProvider;
+import io.grpc.Uri;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
/**
* A provider for {@link DnsNameResolver}.
*
* It resolves a target URI whose scheme is {@code "dns"}. The (optional) authority of the target
- * URI is reserved for the address of alternative DNS server (not implemented yet). The path of the
- * target URI, excluding the leading slash {@code '/'}, is treated as the host name and the optional
- * port to be resolved by DNS. Example target URIs:
+ * URI is reserved for the address of alternative DNS server (not implemented yet). The first path
+ * segment of the hierarchical target URI is interpreted as an RFC 2396 "server-based" authority and
+ * used as the "service authority" of the resulting {@link NameResolver}. The "host" part of this
+ * authority is the name to be resolved by DNS. The "port" part of this authority (if present) will
+ * become the port number for all {@link InetSocketAddress} produced by this resolver. For example:
*
* tel:+1-206-555-1212
+ * , mailto:me@example.com and urn:isbn:978-1492082798 all have
+ * rootless paths. mailto:%2Fdev%2Fnull@example.com is also rootless because its
+ * percent-encoded slashes are not segment delimiters but rather part of the first and only path
+ * segment.
+ *
+ * file:///resume.txt
+ * , file:/resume.txt and file://localhost/ all have absolute
+ * paths while tel:+1-206-555-1212's path is not absolute.
+ * mailto:%2Fdev%2Fnull@example.com is also not absolute because its percent-encoded
+ * slashes are not segment delimiters but rather part of the first and only path segment.
+ *
+ *
*
*/
public final class DnsNameResolverProvider extends NameResolverProvider {
@@ -51,6 +56,7 @@ public final class DnsNameResolverProvider extends NameResolverProvider {
@Override
public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
+ // TODO(jdcormie): Remove once RFC 3986 migration is complete.
if (SCHEME.equals(targetUri.getScheme())) {
String targetPath = Preconditions.checkNotNull(targetUri.getPath(), "targetPath");
Preconditions.checkArgument(targetPath.startsWith("/"),
@@ -68,6 +74,25 @@ public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
}
}
+ @Override
+ public NameResolver newNameResolver(Uri targetUri, final NameResolver.Args args) {
+ if (SCHEME.equals(targetUri.getScheme())) {
+ List