1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.net.ConnectException; 25 import java.net.InetSocketAddress; 26 import java.net.NoRouteToHostException; 27 import java.net.ProxySelector; 28 import java.net.URI; 29 import java.net.http.HttpClient; 30 import java.net.http.HttpClient.Version; 31 import java.net.http.HttpConnectTimeoutException; 32 import java.net.http.HttpRequest; 33 import java.net.http.HttpRequest.BodyPublishers; 34 import java.net.http.HttpResponse; 35 import java.net.http.HttpResponse.BodyHandlers; 36 import java.time.Duration; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.concurrent.CompletionException; 41 import org.testng.annotations.DataProvider; 42 import static java.lang.System.out; 43 import static java.net.http.HttpClient.Builder.NO_PROXY; 44 import static java.net.http.HttpClient.Version.HTTP_1_1; 45 import static java.net.http.HttpClient.Version.HTTP_2; 46 import static java.time.Duration.*; 47 import static java.util.concurrent.TimeUnit.NANOSECONDS; 48 import static org.testng.Assert.fail; 49 50 public abstract class AbstractConnectTimeout { 51 52 static final Duration NO_DURATION = null; 53 54 static List<List<Duration>> TIMEOUTS = List.of( 55 // connectTimeout HttpRequest timeout 56 Arrays.asList( NO_DURATION, ofSeconds(1) ), 57 Arrays.asList( NO_DURATION, ofMillis(100) ), 58 Arrays.asList( NO_DURATION, ofNanos(99) ), 59 Arrays.asList( NO_DURATION, ofNanos(1) ), 60 61 Arrays.asList( ofSeconds(1), NO_DURATION ), 62 Arrays.asList( ofMillis(100), NO_DURATION ), 63 Arrays.asList( ofNanos(99), NO_DURATION ), 64 Arrays.asList( ofNanos(1), NO_DURATION ), 65 66 Arrays.asList( ofSeconds(1), ofMinutes(1) ), 67 Arrays.asList( ofMillis(100), ofMinutes(1) ), 68 Arrays.asList( ofNanos(99), ofMinutes(1) ), 69 Arrays.asList( ofNanos(1), ofMinutes(1) ) 70 ); 71 72 static final List<String> METHODS = List.of("GET", "POST"); 73 static final List<Version> VERSIONS = List.of(HTTP_2, HTTP_1_1); 74 static final List<String> SCHEMES = List.of("https", "http"); 75 76 @DataProvider(name = "variants") variants()77 public Object[][] variants() { 78 List<Object[]> l = new ArrayList<>(); 79 for (List<Duration> timeouts : TIMEOUTS) { 80 Duration connectTimeout = timeouts.get(0); 81 Duration requestTimeout = timeouts.get(1); 82 for (String method: METHODS) { 83 for (String scheme : SCHEMES) { 84 for (Version requestVersion : VERSIONS) { 85 l.add(new Object[] {requestVersion, scheme, method, connectTimeout, requestTimeout}); 86 }}}} 87 return l.stream().toArray(Object[][]::new); 88 } 89 90 static final ProxySelector EXAMPLE_DOT_COM_PROXY = ProxySelector.of( 91 InetSocketAddress.createUnresolved("example.com", 8080)); 92 93 //@Test(dataProvider = "variants") timeoutNoProxySync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout)94 protected void timeoutNoProxySync(Version requestVersion, 95 String scheme, 96 String method, 97 Duration connectTimeout, 98 Duration requestTimeout) 99 throws Exception 100 { 101 timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); 102 } 103 104 //@Test(dataProvider = "variants") timeoutWithProxySync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout)105 protected void timeoutWithProxySync(Version requestVersion, 106 String scheme, 107 String method, 108 Duration connectTimeout, 109 Duration requestTimeout) 110 throws Exception 111 { 112 timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); 113 } 114 timeoutSync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout, ProxySelector proxy)115 private void timeoutSync(Version requestVersion, 116 String scheme, 117 String method, 118 Duration connectTimeout, 119 Duration requestTimeout, 120 ProxySelector proxy) 121 throws Exception 122 { 123 out.printf("%ntimeoutSync(requestVersion=%s, scheme=%s, method=%s," 124 + " connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", 125 requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); 126 127 HttpClient client = newClient(connectTimeout, proxy); 128 HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); 129 130 for (int i = 0; i < 2; i++) { 131 out.printf("iteration %d%n", i); 132 long startTime = System.nanoTime(); 133 try { 134 HttpResponse<?> resp = client.send(request, BodyHandlers.ofString()); 135 printResponse(resp); 136 fail("Unexpected response: " + resp); 137 } catch (HttpConnectTimeoutException expected) { // blocking thread-specific exception 138 long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); 139 out.printf("Client: received in %d millis%n", elapsedTime); 140 assertExceptionTypeAndCause(expected.getCause()); 141 } catch (ConnectException e) { 142 long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); 143 out.printf("Client: received in %d millis%n", elapsedTime); 144 Throwable t = e.getCause().getCause(); // blocking thread-specific exception 145 if (!(t instanceof NoRouteToHostException)) { // tolerate only NRTHE 146 e.printStackTrace(out); 147 fail("Unexpected exception:" + e); 148 } else { 149 out.printf("Caught ConnectException with NoRouteToHostException" 150 + " cause: %s - skipping%n", t.getCause()); 151 } 152 } 153 } 154 } 155 156 //@Test(dataProvider = "variants") timeoutNoProxyAsync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout)157 protected void timeoutNoProxyAsync(Version requestVersion, 158 String scheme, 159 String method, 160 Duration connectTimeout, 161 Duration requestTimeout) { 162 timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); 163 } 164 165 //@Test(dataProvider = "variants") timeoutWithProxyAsync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout)166 protected void timeoutWithProxyAsync(Version requestVersion, 167 String scheme, 168 String method, 169 Duration connectTimeout, 170 Duration requestTimeout) { 171 timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); 172 } 173 timeoutAsync(Version requestVersion, String scheme, String method, Duration connectTimeout, Duration requestTimeout, ProxySelector proxy)174 private void timeoutAsync(Version requestVersion, 175 String scheme, 176 String method, 177 Duration connectTimeout, 178 Duration requestTimeout, 179 ProxySelector proxy) { 180 out.printf("%ntimeoutAsync(requestVersion=%s, scheme=%s, method=%s, " 181 + "connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", 182 requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); 183 184 HttpClient client = newClient(connectTimeout, proxy); 185 HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); 186 for (int i = 0; i < 2; i++) { 187 out.printf("iteration %d%n", i); 188 long startTime = System.nanoTime(); 189 try { 190 HttpResponse<?> resp = client.sendAsync(request, BodyHandlers.ofString()).join(); 191 printResponse(resp); 192 fail("Unexpected response: " + resp); 193 } catch (CompletionException e) { 194 long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); 195 out.printf("Client: received in %d millis%n", elapsedTime); 196 Throwable t = e.getCause(); 197 if (t instanceof ConnectException && 198 t.getCause() instanceof NoRouteToHostException) { // tolerate only NRTHE 199 out.printf("Caught ConnectException with NoRouteToHostException" 200 + " cause: %s - skipping%n", t.getCause()); 201 } else { 202 assertExceptionTypeAndCause(t); 203 } 204 } 205 } 206 } 207 newClient(Duration connectTimeout, ProxySelector proxy)208 static HttpClient newClient(Duration connectTimeout, ProxySelector proxy) { 209 HttpClient.Builder builder = HttpClient.newBuilder().proxy(proxy); 210 if (connectTimeout != NO_DURATION) 211 builder.connectTimeout(connectTimeout); 212 return builder.build(); 213 } 214 newRequest(String scheme, Version reqVersion, String method, Duration requestTimeout)215 static HttpRequest newRequest(String scheme, 216 Version reqVersion, 217 String method, 218 Duration requestTimeout) { 219 // Resolvable address. Most tested environments just ignore the TCP SYN, 220 // or occasionally return ICMP no route to host 221 URI uri = URI.create(scheme +"://example.com:81/"); 222 HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(uri); 223 reqBuilder = reqBuilder.version(reqVersion); 224 switch (method) { 225 case "GET" : reqBuilder.GET(); break; 226 case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break; 227 default: throw new AssertionError("Unknown method:" + method); 228 } 229 if (requestTimeout != NO_DURATION) 230 reqBuilder.timeout(requestTimeout); 231 return reqBuilder.build(); 232 } 233 assertExceptionTypeAndCause(Throwable t)234 static void assertExceptionTypeAndCause(Throwable t) { 235 if (!(t instanceof HttpConnectTimeoutException)) { 236 t.printStackTrace(out); 237 fail("Expected HttpConnectTimeoutException, got:" + t); 238 } 239 Throwable connEx = t.getCause(); 240 if (!(connEx instanceof ConnectException)) { 241 t.printStackTrace(out); 242 fail("Expected ConnectException cause in:" + connEx); 243 } 244 out.printf("Caught expected HttpConnectTimeoutException with ConnectException" 245 + " cause: %n%s%n%s%n", t, connEx); 246 final String EXPECTED_MESSAGE = "HTTP connect timed out"; // impl dependent 247 if (!connEx.getMessage().equals(EXPECTED_MESSAGE)) 248 fail("Expected: \"" + EXPECTED_MESSAGE + "\", got: \"" + connEx.getMessage() + "\""); 249 250 } 251 printResponse(HttpResponse<?> response)252 static void printResponse(HttpResponse<?> response) { 253 out.println("Unexpected response: " + response); 254 out.println("Headers: " + response.headers()); 255 out.println("Body: " + response.body()); 256 } 257 } 258