/* * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.IOException; import java.lang.reflect.Method; import java.net.Authenticator; import java.net.CookieHandler; import java.net.CookieManager; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandler; import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.PushPromiseHandler; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import java.net.http.HttpClient; import java.net.http.HttpClient.Redirect; import java.net.http.HttpClient.Version; import java.util.concurrent.atomic.AtomicInteger; import jdk.test.lib.net.SimpleSSLContext; import org.testng.annotations.Test; import static java.time.Duration.*; import static org.testng.Assert.*; /* * @test * @bug 8209137 * @summary HttpClient[.Builder] API and behaviour checks * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext * @run testng HttpClientBuilderTest */ public class HttpClientBuilderTest { static final Class NPE = NullPointerException.class; static final Class IAE = IllegalArgumentException.class; static final Class UOE = UnsupportedOperationException.class; @Test public void testDefaults() throws Exception { List clients = List.of(HttpClient.newHttpClient(), HttpClient.newBuilder().build()); for (HttpClient c : clients) { // Empty optionals and defaults try (var client = c) { assertFalse(client.authenticator().isPresent()); assertFalse(client.cookieHandler().isPresent()); assertFalse(client.connectTimeout().isPresent()); assertFalse(client.executor().isPresent()); assertFalse(client.proxy().isPresent()); assertTrue(client.sslParameters() != null); assertTrue(client.followRedirects().equals(HttpClient.Redirect.NEVER)); assertTrue(client.sslContext() == SSLContext.getDefault()); assertTrue(client.version().equals(HttpClient.Version.HTTP_2)); } } } @Test public void testNull() throws Exception { HttpClient.Builder builder = HttpClient.newBuilder(); assertThrows(NPE, () -> builder.authenticator(null)); assertThrows(NPE, () -> builder.cookieHandler(null)); assertThrows(NPE, () -> builder.connectTimeout(null)); assertThrows(NPE, () -> builder.executor(null)); assertThrows(NPE, () -> builder.proxy(null)); assertThrows(NPE, () -> builder.sslParameters(null)); assertThrows(NPE, () -> builder.followRedirects(null)); assertThrows(NPE, () -> builder.sslContext(null)); assertThrows(NPE, () -> builder.version(null)); } static class TestAuthenticator extends Authenticator { } static class Closer implements AutoCloseable { final HttpClient.Builder builder; HttpClient client; Closer(HttpClient.Builder builder) { this.builder = Objects.requireNonNull(builder); } HttpClient build() { if (client != null) client.close(); return client = builder.build(); } @Override public void close() { if (client != null) client.close(); } } static Closer closeable(HttpClient.Builder builder) { return new Closer(builder); } @Test public void testAuthenticator() { HttpClient.Builder builder = HttpClient.newBuilder(); Authenticator a = new TestAuthenticator(); builder.authenticator(a); try (var closer = closeable(builder)) { assertTrue(closer.build().authenticator().get() == a); } Authenticator b = new TestAuthenticator(); builder.authenticator(b); try (var closer = closeable(builder)) { assertTrue(closer.build().authenticator().get() == b); } assertThrows(NPE, () -> builder.authenticator(null)); Authenticator c = new TestAuthenticator(); builder.authenticator(c); try (var closer = closeable(builder)) { assertTrue(closer.build().authenticator().get() == c); } } @Test public void testCookieHandler() { HttpClient.Builder builder = HttpClient.newBuilder(); CookieHandler a = new CookieManager(); builder.cookieHandler(a); try (var closer = closeable(builder)) { assertTrue(closer.build().cookieHandler().get() == a); } CookieHandler b = new CookieManager(); builder.cookieHandler(b); try (var closer = closeable(builder)) { assertTrue(closer.build().cookieHandler().get() == b); } assertThrows(NPE, () -> builder.cookieHandler(null)); CookieManager c = new CookieManager(); builder.cookieHandler(c); try (var closer = closeable(builder)) { assertTrue(closer.build().cookieHandler().get() == c); } } @Test public void testConnectTimeout() { HttpClient.Builder builder = HttpClient.newBuilder(); Duration a = Duration.ofSeconds(5); builder.connectTimeout(a); try (var closer = closeable(builder)) { assertTrue(closer.build().connectTimeout().get() == a); } Duration b = Duration.ofMinutes(1); builder.connectTimeout(b); try (var closer = closeable(builder)) { assertTrue(closer.build().connectTimeout().get() == b); } assertThrows(NPE, () -> builder.cookieHandler(null)); Duration c = Duration.ofHours(100); builder.connectTimeout(c); try (var closer = closeable(builder)) { assertTrue(closer.build().connectTimeout().get() == c); } assertThrows(IAE, () -> builder.connectTimeout(ZERO)); assertThrows(IAE, () -> builder.connectTimeout(ofSeconds(0))); assertThrows(IAE, () -> builder.connectTimeout(ofSeconds(-1))); assertThrows(IAE, () -> builder.connectTimeout(ofNanos(-100))); } static class TestExecutor implements Executor { public void execute(Runnable r) { r.run();} } @Test public void testExecutor() { HttpClient.Builder builder = HttpClient.newBuilder(); TestExecutor a = new TestExecutor(); builder.executor(a); try (var closer = closeable(builder)) { assertTrue(closer.build().executor().get() == a); } TestExecutor b = new TestExecutor(); builder.executor(b); try (var closer = closeable(builder)) { assertTrue(closer.build().executor().get() == b); } assertThrows(NPE, () -> builder.executor(null)); TestExecutor c = new TestExecutor(); builder.executor(c); try (var closer = closeable(builder)) { assertTrue(closer.build().executor().get() == c); } } @Test public void testProxySelector() { HttpClient.Builder builder = HttpClient.newBuilder(); ProxySelector a = ProxySelector.of(null); builder.proxy(a); try (var closer = closeable(builder)) { assertTrue(closer.build().proxy().get() == a); } ProxySelector b = ProxySelector.of(InetSocketAddress.createUnresolved("foo", 80)); builder.proxy(b); try (var closer = closeable(builder)) { assertTrue(closer.build().proxy().get() == b); } assertThrows(NPE, () -> builder.proxy(null)); ProxySelector c = ProxySelector.of(InetSocketAddress.createUnresolved("bar", 80)); builder.proxy(c); try (var closer = closeable(builder)) { assertTrue(closer.build().proxy().get() == c); } } @Test public void testSSLParameters() { HttpClient.Builder builder = HttpClient.newBuilder(); SSLParameters a = new SSLParameters(); a.setCipherSuites(new String[] { "A" }); builder.sslParameters(a); a.setCipherSuites(new String[] { "Z" }); try (var closer = closeable(builder)) { assertTrue(closer.build().sslParameters() != (a)); } try (var closer = closeable(builder)) { assertTrue(closer.build().sslParameters().getCipherSuites()[0].equals("A")); } SSLParameters b = new SSLParameters(); b.setEnableRetransmissions(true); builder.sslParameters(b); try (var closer = closeable(builder)) { assertTrue(closer.build().sslParameters() != b); } try (var closer = closeable(builder)) { assertTrue(closer.build().sslParameters().getEnableRetransmissions()); } assertThrows(NPE, () -> builder.sslParameters(null)); SSLParameters c = new SSLParameters(); c.setProtocols(new String[] { "C" }); builder.sslParameters(c); c.setProtocols(new String[] { "D" }); try (var closer = closeable(builder)) { assertTrue(closer.build().sslParameters().getProtocols()[0].equals("C")); } } @Test public void testSSLContext() throws Exception { HttpClient.Builder builder = HttpClient.newBuilder(); SSLContext a = (new SimpleSSLContext()).get(); builder.sslContext(a); try (var closer = closeable(builder)) { assertTrue(closer.build().sslContext() == a); } SSLContext b = (new SimpleSSLContext()).get(); builder.sslContext(b); try (var closer = closeable(builder)) { assertTrue(closer.build().sslContext() == b); } assertThrows(NPE, () -> builder.sslContext(null)); SSLContext c = (new SimpleSSLContext()).get(); builder.sslContext(c); try (var closer = closeable(builder)) { assertTrue(closer.build().sslContext() == c); } } @Test public void testFollowRedirects() { HttpClient.Builder builder = HttpClient.newBuilder(); builder.followRedirects(Redirect.ALWAYS); try (var closer = closeable(builder)) { assertTrue(closer.build().followRedirects() == Redirect.ALWAYS); } builder.followRedirects(Redirect.NEVER); try (var closer = closeable(builder)) { assertTrue(closer.build().followRedirects() == Redirect.NEVER); } assertThrows(NPE, () -> builder.followRedirects(null)); builder.followRedirects(Redirect.NORMAL); try (var closer = closeable(builder)) { assertTrue(closer.build().followRedirects() == Redirect.NORMAL); } } @Test public void testVersion() { HttpClient.Builder builder = HttpClient.newBuilder(); builder.version(Version.HTTP_2); try (var closer = closeable(builder)) { assertTrue(closer.build().version() == Version.HTTP_2); } builder.version(Version.HTTP_1_1); try (var closer = closeable(builder)) { assertTrue(closer.build().version() == Version.HTTP_1_1); } assertThrows(NPE, () -> builder.version(null)); builder.version(Version.HTTP_2); try (var closer = closeable(builder)) { assertTrue(closer.build().version() == Version.HTTP_2); } builder.version(Version.HTTP_1_1); try (var closer = closeable(builder)) { assertTrue(closer.build().version() == Version.HTTP_1_1); } } @Test static void testPriority() throws Exception { HttpClient.Builder builder = HttpClient.newBuilder(); assertThrows(IAE, () -> builder.priority(-1)); assertThrows(IAE, () -> builder.priority(0)); assertThrows(IAE, () -> builder.priority(257)); assertThrows(IAE, () -> builder.priority(500)); builder.priority(1); try (var httpClient = builder.build()) {} builder.priority(256); try (var httpClient = builder.build()) {} } /** * Tests the {@link java.net.http.HttpClient.Builder#localAddress(InetAddress)} method * behaviour when that method is called on a builder returned by {@link HttpClient#newBuilder()} */ @Test public void testLocalAddress() throws Exception { HttpClient.Builder builder = HttpClient.newBuilder(); // setting null should work fine builder.localAddress(null); builder.localAddress(InetAddress.getLoopbackAddress()); // resetting back to null should work fine builder.localAddress(null); } /** * Tests that the default method implementation of * {@link java.net.http.HttpClient.Builder#localAddress(InetAddress)} throws * an {@link UnsupportedOperationException} */ @Test public void testDefaultMethodImplForLocalAddress() throws Exception { HttpClient.Builder noOpBuilder = new HttpClient.Builder() { @Override public HttpClient.Builder cookieHandler(CookieHandler cookieHandler) { return null; } @Override public HttpClient.Builder connectTimeout(Duration duration) { return null; } @Override public HttpClient.Builder sslContext(SSLContext sslContext) { return null; } @Override public HttpClient.Builder sslParameters(SSLParameters sslParameters) { return null; } @Override public HttpClient.Builder executor(Executor executor) { return null; } @Override public HttpClient.Builder followRedirects(Redirect policy) { return null; } @Override public HttpClient.Builder version(Version version) { return null; } @Override public HttpClient.Builder priority(int priority) { return null; } @Override public HttpClient.Builder proxy(ProxySelector proxySelector) { return null; } @Override public HttpClient.Builder authenticator(Authenticator authenticator) { return null; } @Override public HttpClient build() { return null; } }; // expected to throw a UnsupportedOperationException assertThrows(UOE, () -> noOpBuilder.localAddress(null)); // a non-null address should also throw a UnsupportedOperationException assertThrows(UOE, () -> noOpBuilder.localAddress(InetAddress.getLoopbackAddress())); } // --- static final URI uri = URI.create("http://foo.com/"); @Test static void testHttpClientSendArgs() throws Exception { try (HttpClient client = HttpClient.newHttpClient()) { HttpRequest request = HttpRequest.newBuilder(uri).build(); assertThrows(NPE, () -> client.send(null, BodyHandlers.discarding())); assertThrows(NPE, () -> client.send(request, null)); assertThrows(NPE, () -> client.send(null, null)); assertThrows(NPE, () -> client.sendAsync(null, BodyHandlers.discarding())); assertThrows(NPE, () -> client.sendAsync(request, null)); assertThrows(NPE, () -> client.sendAsync(null, null)); assertThrows(NPE, () -> client.sendAsync(null, BodyHandlers.discarding(), null)); assertThrows(NPE, () -> client.sendAsync(request, null, null)); assertThrows(NPE, () -> client.sendAsync(null, null, null)); // CONNECT is disallowed in the implementation, since it is used for // tunneling, and is handled separately for security checks. HttpRequest connectRequest = new HttpConnectRequest(); assertThrows(IAE, () -> client.send(connectRequest, BodyHandlers.discarding())); assertThrows(IAE, () -> client.sendAsync(connectRequest, BodyHandlers.discarding())); assertThrows(IAE, () -> client.sendAsync(connectRequest, BodyHandlers.discarding(), null)); } } static class HttpConnectRequest extends HttpRequest { @Override public Optional bodyPublisher() { return Optional.empty(); } @Override public String method() { return "CONNECT"; } @Override public Optional timeout() { return Optional.empty(); } @Override public boolean expectContinue() { return false; } @Override public URI uri() { return URI.create("http://foo.com/"); } @Override public Optional version() { return Optional.empty(); } @Override public HttpHeaders headers() { return HttpHeaders.of(Map.of(), (x, y) -> true); } } // --- @Test static void testUnsupportedWebSocket() throws Exception { // @implSpec The default implementation of this method throws // {@code UnsupportedOperationException}. assertThrows(UOE, () -> (new MockHttpClient()).newWebSocketBuilder()); } @Test static void testDefaultShutdown() throws Exception { try (HttpClient client = new MockHttpClient()) { client.shutdown(); // does nothing } } @Test static void testDefaultShutdownNow() throws Exception { try (HttpClient client = new MockHttpClient()) { client.shutdownNow(); // calls shutdown, doesn't wait } AtomicInteger shutdownCalled = new AtomicInteger(); HttpClient mock = new MockHttpClient() { @Override public void shutdown() { super.shutdown(); shutdownCalled.incrementAndGet(); } }; try (HttpClient client = mock) { client.shutdownNow(); // calls shutdown, doesn't wait } // once from shutdownNow(), and once from close() assertEquals(shutdownCalled.get(), 2); } @Test static void testDefaultIsTerminated() throws Exception { try (HttpClient client = new MockHttpClient()) { assertFalse(client.isTerminated()); } } @Test static void testDefaultAwaitTermination() throws Exception { try (HttpClient client = new MockHttpClient()) { assertTrue(client.awaitTermination(Duration.ofDays(1))); } try (HttpClient client = new MockHttpClient()) { assertThrows(NullPointerException.class, () -> client.awaitTermination(null)); } } @Test static void testDefaultClose() { AtomicInteger shutdownCalled = new AtomicInteger(); AtomicInteger awaitTerminationCalled = new AtomicInteger(); AtomicInteger shutdownNowCalled = new AtomicInteger(); HttpClient mock = new MockHttpClient() { @Override public void shutdown() { super.shutdown(); shutdownCalled.incrementAndGet(); } @Override public void shutdownNow() { super.shutdownNow(); shutdownNowCalled.incrementAndGet(); } @Override public boolean awaitTermination(Duration duration) throws InterruptedException { int count = awaitTerminationCalled.incrementAndGet(); if (count == 1) return false; if (count == 2) return true; if (count == 3) { Thread.currentThread().interrupt(); throw new InterruptedException(); } return super.awaitTermination(duration); } }; // first time around: // close() // shutdown() 0->1 // awaitTermination() 0->1 -> false // awaitTermination() 1->2 -> true try (HttpClient client = mock) { } assertEquals(shutdownCalled.get(), 1); // called by close() assertEquals(shutdownNowCalled.get(), 0); // not called assertEquals(awaitTerminationCalled.get(), 2); // called by close() twice assertFalse(Thread.currentThread().isInterrupted()); // second time around: // close() // shutdown() 1->2 // awaitTermination() 2->3 -> interrupt, throws // shutdownNow() 0->1 // calls shutdown() 2->3 // awaitTermination() 3->4 -> true try (HttpClient client = mock) { } assertEquals(shutdownCalled.get(), 3); // called by close() and shutdownNow() assertEquals(shutdownNowCalled.get(), 1); // called by close() due to interrupt assertEquals(awaitTerminationCalled.get(), 4); // called by close twice assertTrue(Thread.currentThread().isInterrupted()); assertTrue(Thread.interrupted()); } static class MockHttpClient extends HttpClient { @Override public Optional cookieHandler() { return null; } @Override public Optional connectTimeout() { return null; } @Override public Redirect followRedirects() { return null; } @Override public Optional proxy() { return null; } @Override public SSLContext sslContext() { return null; } @Override public SSLParameters sslParameters() { return null; } @Override public Optional authenticator() { return null; } @Override public Version version() { return null; } @Override public Optional executor() { return null; } @Override public HttpResponse send(HttpRequest request, BodyHandler responseBodyHandler) throws IOException, InterruptedException { return null; } @Override public CompletableFuture> sendAsync(HttpRequest request, BodyHandler responseBodyHandler) { return null; } @Override public CompletableFuture> sendAsync(HttpRequest x, BodyHandler y, PushPromiseHandler z) { return null; } } /* ---- standalone entry point ---- */ public static void main(String[] args) throws Exception { HttpClientBuilderTest test = new HttpClientBuilderTest(); for (Method m : HttpClientBuilderTest.class.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { try { m.invoke(test); System.out.printf("test %s: success%n", m.getName()); } catch (Throwable t ) { System.out.printf("test %s: failed%n", m.getName()); t.printStackTrace(); } } } } }