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