--- /dev/null 2018-08-07 15:14:28.000000000 +0100 +++ new/test/jdk/java/net/httpclient/AbstractConnectTimeout.java 2018-08-07 15:14:27.000000000 +0100 @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2018, 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.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.NoRouteToHostException; +import java.net.ProxySelector; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletionException; +import org.testng.annotations.DataProvider; +import static java.lang.System.out; +import static java.net.http.HttpClient.Builder.NO_PROXY; +import static java.net.http.HttpClient.Version.HTTP_1_1; +import static java.net.http.HttpClient.Version.HTTP_2; +import static java.time.Duration.*; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.testng.Assert.fail; + +public abstract class AbstractConnectTimeout { + + static final Duration NO_DURATION = null; + + static List> TIMEOUTS = List.of( + // connectTimeout HttpRequest timeout + Arrays.asList( NO_DURATION, ofSeconds(1) ), + Arrays.asList( NO_DURATION, ofMillis(100) ), + Arrays.asList( NO_DURATION, ofNanos(99) ), + Arrays.asList( NO_DURATION, ofNanos(1) ), + + Arrays.asList( ofSeconds(1), NO_DURATION ), + Arrays.asList( ofMillis(100), NO_DURATION ), + Arrays.asList( ofNanos(99), NO_DURATION ), + Arrays.asList( ofNanos(1), NO_DURATION ), + + Arrays.asList( ofSeconds(1), ofMinutes(1) ), + Arrays.asList( ofMillis(100), ofMinutes(1) ), + Arrays.asList( ofNanos(99), ofMinutes(1) ), + Arrays.asList( ofNanos(1), ofMinutes(1) ) + ); + + static final List METHODS = List.of("GET", "POST"); + static final List VERSIONS = List.of(HTTP_2, HTTP_1_1); + static final List SCHEMES = List.of("https", "http"); + + @DataProvider(name = "variants") + public Object[][] variants() { + List l = new ArrayList<>(); + for (List timeouts : TIMEOUTS) { + Duration connectTimeout = timeouts.get(0); + Duration requestTimeout = timeouts.get(1); + for (String method: METHODS) { + for (String scheme : SCHEMES) { + for (Version requestVersion : VERSIONS) { + l.add(new Object[] {requestVersion, scheme, method, connectTimeout, requestTimeout}); + }}}} + return l.stream().toArray(Object[][]::new); + } + + static final ProxySelector EXAMPLE_DOT_COM_PROXY = ProxySelector.of( + InetSocketAddress.createUnresolved("example.com", 8080)); + + //@Test(dataProvider = "variants") + protected void timeoutNoProxySync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout) + throws Exception + { + timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); + } + + //@Test(dataProvider = "variants") + protected void timeoutWithProxySync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout) + throws Exception + { + timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); + } + + private void timeoutSync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout, + ProxySelector proxy) + throws Exception + { + out.printf("%ntimeoutSync(requestVersion=%s, scheme=%s, method=%s," + + " connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", + requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); + + HttpClient client = newClient(connectTimeout, proxy); + HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); + + for (int i = 0; i < 2; i++) { + out.printf("iteration %d%n", i); + long startTime = System.nanoTime(); + try { + HttpResponse resp = client.send(request, BodyHandlers.ofString()); + printResponse(resp); + fail("Unexpected response: " + resp); + } catch (HttpConnectTimeoutException expected) { // blocking thread-specific exception + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + out.printf("Client: received in %d millis%n", elapsedTime); + assertExceptionTypeAndCause(expected.getCause()); + } catch (ConnectException e) { + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + out.printf("Client: received in %d millis%n", elapsedTime); + Throwable t = e.getCause().getCause(); // blocking thread-specific exception + if (!(t instanceof NoRouteToHostException)) { // tolerate only NRTHE + e.printStackTrace(out); + fail("Unexpected exception:" + e); + } else { + out.printf("Caught ConnectException with NoRouteToHostException" + + " cause: %s - skipping%n", t.getCause()); + } + } + } + } + + //@Test(dataProvider = "variants") + protected void timeoutNoProxyAsync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout) { + timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); + } + + //@Test(dataProvider = "variants") + protected void timeoutWithProxyAsync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout) { + timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); + } + + private void timeoutAsync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout, + ProxySelector proxy) { + out.printf("%ntimeoutAsync(requestVersion=%s, scheme=%s, method=%s, " + + "connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", + requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); + + HttpClient client = newClient(connectTimeout, proxy); + HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); + for (int i = 0; i < 2; i++) { + out.printf("iteration %d%n", i); + long startTime = System.nanoTime(); + try { + HttpResponse resp = client.sendAsync(request, BodyHandlers.ofString()).join(); + printResponse(resp); + fail("Unexpected response: " + resp); + } catch (CompletionException e) { + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + out.printf("Client: received in %d millis%n", elapsedTime); + Throwable t = e.getCause(); + if (t instanceof ConnectException && + t.getCause() instanceof NoRouteToHostException) { // tolerate only NRTHE + out.printf("Caught ConnectException with NoRouteToHostException" + + " cause: %s - skipping%n", t.getCause()); + } else { + assertExceptionTypeAndCause(t); + } + } + } + } + + static HttpClient newClient(Duration connectTimeout, ProxySelector proxy) { + HttpClient.Builder builder = HttpClient.newBuilder().proxy(proxy); + if (connectTimeout != NO_DURATION) + builder.connectTimeout(connectTimeout); + return builder.build(); + } + + static HttpRequest newRequest(String scheme, + Version reqVersion, + String method, + Duration requestTimeout) { + // Resolvable address. Most tested environments just ignore the TCP SYN, + // or occasionally return ICMP no route to host + URI uri = URI.create(scheme +"://example.com:81/"); + HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(uri); + reqBuilder = reqBuilder.version(reqVersion); + switch (method) { + case "GET" : reqBuilder.GET(); break; + case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break; + default: throw new AssertionError("Unknown method:" + method); + } + if (requestTimeout != NO_DURATION) + reqBuilder.timeout(requestTimeout); + return reqBuilder.build(); + } + + static void assertExceptionTypeAndCause(Throwable t) { + if (!(t instanceof HttpConnectTimeoutException)) { + t.printStackTrace(out); + fail("Expected HttpConnectTimeoutException, got:" + t); + } + Throwable connEx = t.getCause(); + if (!(connEx instanceof ConnectException)) { + t.printStackTrace(out); + fail("Expected ConnectException cause in:" + connEx); + } + out.printf("Caught expected HttpConnectTimeoutException with ConnectException" + + " cause: %n%s%n%s%n", t, connEx); + final String EXPECTED_MESSAGE = "HTTP connect timed out"; // impl dependent + if (!connEx.getMessage().equals(EXPECTED_MESSAGE)) + fail("Expected: \"" + EXPECTED_MESSAGE + "\", got: \"" + connEx.getMessage() + "\""); + + } + + static void printResponse(HttpResponse response) { + out.println("Unexpected response: " + response); + out.println("Headers: " + response.headers()); + out.println("Body: " + response.body()); + } +}