diff --git a/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java index 58c7803346f..2f9903eac03 100644 --- a/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java +++ b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java @@ -207,6 +207,14 @@ private ProxiedSocketAddress detectProxy(InetSocketAddress targetAddr) throws IO } List proxies = proxySelector.select(uri); + // ProxySelector.select(URI) is contractually required to return a non-null, non-empty list. + // Surface the offending implementation's class name so a broken ProxySelector can be fixed. + if (proxies == null || proxies.isEmpty()) { + throw new IOException( + "ProxySelector " + proxySelector.getClass().getName() + + " returned " + (proxies == null ? "null" : "an empty list") + + ", which violates the java.net.ProxySelector#select(URI) contract"); + } if (proxies.size() > 1) { log.warning("More than 1 proxy detected, gRPC will select the first one"); } diff --git a/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java b/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java index 771050f119d..af0ed1f35d3 100644 --- a/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java +++ b/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +34,7 @@ import io.grpc.HttpConnectProxiedSocketAddress; import io.grpc.ProxiedSocketAddress; import io.grpc.ProxyDetector; +import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; @@ -40,6 +42,7 @@ import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; +import java.util.Collections; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -191,4 +194,24 @@ public ProxySelector get() { authenticator); assertNull(proxyDetector.proxyFor(destination)); } + + @Test + public void throwsWhenProxySelectorReturnsEmptyList() throws Exception { + when(proxySelector.select(any(URI.class))).thenReturn(Collections.emptyList()); + + IOException e = + assertThrows(IOException.class, () -> proxyDetector.proxyFor(destination)); + assertTrue(e.getMessage(), e.getMessage().contains("empty list")); + assertTrue(e.getMessage(), e.getMessage().contains(proxySelector.getClass().getName())); + } + + @Test + public void throwsWhenProxySelectorReturnsNullList() throws Exception { + when(proxySelector.select(any(URI.class))).thenReturn(null); + + IOException e = + assertThrows(IOException.class, () -> proxyDetector.proxyFor(destination)); + assertTrue(e.getMessage(), e.getMessage().contains("null")); + assertTrue(e.getMessage(), e.getMessage().contains(proxySelector.getClass().getName())); + } }