1 /*
2  * Copyright (c) 2015, 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 /*
25  * @test
26  * @bug 8156514
27  * @library /lib/testlibrary server
28  * @build jdk.testlibrary.SimpleSSLContext
29  * @modules java.base/sun.net.www.http
30  *          java.net.http/jdk.internal.net.http.common
31  *          java.net.http/jdk.internal.net.http.frame
32  *          java.net.http/jdk.internal.net.http.hpack
33  * @run testng/othervm
34  *      -Djdk.httpclient.HttpClient.log=frames,ssl,requests,responses,errors
35  *      -Djdk.internal.httpclient.debug=true
36  *      RedirectTest
37  */
38 
39 import java.net.InetSocketAddress;
40 import java.net.URI;
41 import java.net.http.HttpClient;
42 import java.net.http.HttpRequest;
43 import java.net.http.HttpRequest.BodyPublishers;
44 import java.net.http.HttpResponse;
45 import java.net.http.HttpResponse.BodyHandlers;
46 import java.util.concurrent.*;
47 import java.util.function.*;
48 import java.util.Arrays;
49 import java.util.Iterator;
50 import org.testng.annotations.Test;
51 import static java.net.http.HttpClient.Version.HTTP_2;
52 
53 public class RedirectTest {
54     static int httpPort;
55     static Http2TestServer httpServer;
56     static HttpClient client;
57 
58     static String httpURIString, altURIString1, altURIString2;
59     static URI httpURI, altURI1, altURI2;
60 
sup(String... args)61     static Supplier<String> sup(String... args) {
62         Iterator<String> i = Arrays.asList(args).iterator();
63         // need to know when to stop calling it.
64         return () -> i.next();
65     }
66 
67     static class Redirector extends Http2RedirectHandler {
68         private InetSocketAddress remoteAddr;
69         private boolean error = false;
70 
Redirector(Supplier<String> supplier)71         Redirector(Supplier<String> supplier) {
72             super(supplier);
73         }
74 
examineExchange(Http2TestExchange ex)75         protected synchronized void examineExchange(Http2TestExchange ex) {
76             InetSocketAddress addr = ex.getRemoteAddress();
77             if (remoteAddr == null) {
78                 remoteAddr = addr;
79                 return;
80             }
81             // check that the client addr/port stays the same, proving
82             // that the connection didn't get dropped.
83             if (!remoteAddr.equals(addr)) {
84                 System.err.printf("Error %s/%s\n", remoteAddr.toString(),
85                         addr.toString());
86                 error = true;
87             }
88         }
89 
error()90         public synchronized boolean error() {
91             return error;
92         }
93     }
94 
initialize()95     static void initialize() throws Exception {
96         try {
97             client = getClient();
98             httpServer = new Http2TestServer(false, 0, null, null);
99             httpPort = httpServer.getAddress().getPort();
100 
101             // urls are accessed in sequence below. The first two are on
102             // different servers. Third on same server as second. So, the
103             // client should use the same http connection.
104             httpURIString = "http://localhost:" + httpPort + "/foo/";
105             httpURI = URI.create(httpURIString);
106             altURIString1 = "http://localhost:" + httpPort + "/redir";
107             altURI1 = URI.create(altURIString1);
108             altURIString2 = "http://localhost:" + httpPort + "/redir_again";
109             altURI2 = URI.create(altURIString2);
110 
111             Redirector r = new Redirector(sup(altURIString1, altURIString2));
112             httpServer.addHandler(r, "/foo");
113             httpServer.addHandler(r, "/redir");
114             httpServer.addHandler(new Http2EchoHandler(), "/redir_again");
115 
116             httpServer.start();
117         } catch (Throwable e) {
118             System.err.println("Throwing now");
119             e.printStackTrace();
120             throw e;
121         }
122     }
123 
124     @Test
test()125     public static void test() throws Exception {
126         try {
127             initialize();
128             simpleTest();
129         } finally {
130             httpServer.stop();
131         }
132     }
133 
getClient()134     static HttpClient getClient() {
135         if (client == null) {
136             client = HttpClient.newBuilder()
137                                .followRedirects(HttpClient.Redirect.ALWAYS)
138                                .version(HTTP_2)
139                                .build();
140         }
141         return client;
142     }
143 
getURI()144     static URI getURI() {
145         return URI.create(httpURIString);
146     }
147 
checkStatus(int expected, int found)148     static void checkStatus(int expected, int found) throws Exception {
149         if (expected != found) {
150             System.err.printf ("Test failed: wrong status code %d/%d\n",
151                 expected, found);
152             throw new RuntimeException("Test failed");
153         }
154     }
155 
checkURIs(URI expected, URI found)156     static void checkURIs(URI expected, URI found) throws Exception {
157         System.out.printf ("Expected: %s, Found: %s\n", expected.toString(), found.toString());
158         if (!expected.equals(found)) {
159             System.err.printf ("Test failed: wrong URI %s/%s\n",
160                 expected.toString(), found.toString());
161             throw new RuntimeException("Test failed");
162         }
163     }
164 
checkStrings(String expected, String found)165     static void checkStrings(String expected, String found) throws Exception {
166         if (!expected.equals(found)) {
167             System.err.printf ("Test failed: wrong string %s/%s\n",
168                 expected, found);
169             throw new RuntimeException("Test failed");
170         }
171     }
172 
check(boolean cond, Object... msg)173     static void check(boolean cond, Object... msg) {
174         if (cond)
175             return;
176         StringBuilder sb = new StringBuilder();
177         for (Object o : msg)
178             sb.append(o);
179         throw new RuntimeException(sb.toString());
180     }
181 
182     static final String SIMPLE_STRING = "Hello world Goodbye world";
183 
simpleTest()184     static void simpleTest() throws Exception {
185         URI uri = getURI();
186         System.err.println("Request to " + uri);
187 
188         HttpClient client = getClient();
189         HttpRequest req = HttpRequest.newBuilder(uri)
190                                      .POST(BodyPublishers.ofString(SIMPLE_STRING))
191                                      .build();
192         CompletableFuture<HttpResponse<String>> cf = client.sendAsync(req, BodyHandlers.ofString());
193         HttpResponse<String> response = cf.join();
194 
195         checkStatus(200, response.statusCode());
196         String responseBody = response.body();
197         checkStrings(SIMPLE_STRING, responseBody);
198         checkURIs(response.uri(), altURI2);
199 
200         // check two previous responses
201         HttpResponse<String> prev = response.previousResponse()
202             .orElseThrow(() -> new RuntimeException("no previous response"));
203         checkURIs(prev.uri(), altURI1);
204 
205         prev = prev.previousResponse()
206             .orElseThrow(() -> new RuntimeException("no previous response"));
207         checkURIs(prev.uri(), httpURI);
208 
209         checkPreviousRedirectResponses(req, response);
210 
211         System.err.println("DONE");
212     }
213 
checkPreviousRedirectResponses(HttpRequest initialRequest, HttpResponse<?> finalResponse)214     static void checkPreviousRedirectResponses(HttpRequest initialRequest,
215                                                HttpResponse<?> finalResponse) {
216         // there must be at least one previous response
217         finalResponse.previousResponse()
218                 .orElseThrow(() -> new RuntimeException("no previous response"));
219 
220         HttpResponse<?> response = finalResponse;
221         do {
222             URI uri = response.uri();
223             response = response.previousResponse().get();
224             check(300 <= response.statusCode() && response.statusCode() <= 309,
225                     "Expected 300 <= code <= 309, got:" + response.statusCode());
226             check(response.body() == null, "Unexpected body: " + response.body());
227             String locationHeader = response.headers().firstValue("Location")
228                     .orElseThrow(() -> new RuntimeException("no previous Location"));
229             check(uri.toString().endsWith(locationHeader),
230                     "URI: " + uri + ", Location: " + locationHeader);
231         } while (response.previousResponse().isPresent());
232 
233         // initial
234         check(initialRequest.equals(response.request()),
235                 "Expected initial request [%s] to equal last prev req [%s]",
236                 initialRequest, response.request());
237     }
238 }
239