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