test/jdk/java/net/httpclient/HttpRedirectTest.java
changeset 58758 2b13d126a2d8
equal deleted inserted replaced
58756:b7aa58d7f5aa 58758:2b13d126a2d8
       
     1 /*
       
     2  * Copyright (c) 2019, 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 import com.sun.net.httpserver.HttpServer;
       
    24 import com.sun.net.httpserver.HttpsConfigurator;
       
    25 import com.sun.net.httpserver.HttpsServer;
       
    26 import jdk.test.lib.net.SimpleSSLContext;
       
    27 import org.testng.annotations.BeforeClass;
       
    28 import org.testng.annotations.AfterClass;
       
    29 import org.testng.annotations.DataProvider;
       
    30 import org.testng.annotations.Test;
       
    31 import static org.testng.Assert.*;
       
    32 
       
    33 import javax.net.ssl.SSLContext;
       
    34 import java.io.IOException;
       
    35 import java.io.InputStream;
       
    36 import java.io.OutputStream;
       
    37 import java.net.InetAddress;
       
    38 import java.net.InetSocketAddress;
       
    39 import java.net.Proxy;
       
    40 import java.net.ProxySelector;
       
    41 import java.net.SocketAddress;
       
    42 import java.net.URI;
       
    43 import java.net.URISyntaxException;
       
    44 import java.net.http.HttpClient;
       
    45 import java.net.http.HttpRequest;
       
    46 import java.net.http.HttpResponse;
       
    47 import java.nio.charset.StandardCharsets;
       
    48 import java.util.List;
       
    49 import java.util.Map;
       
    50 import java.util.Random;
       
    51 import java.util.Set;
       
    52 import java.util.concurrent.CompletableFuture;
       
    53 import java.util.concurrent.CopyOnWriteArrayList;
       
    54 import java.util.concurrent.CopyOnWriteArraySet;
       
    55 import java.util.concurrent.ExecutorService;
       
    56 import java.util.concurrent.LinkedBlockingQueue;
       
    57 import java.util.concurrent.ThreadPoolExecutor;
       
    58 import java.util.concurrent.TimeUnit;
       
    59 import java.util.concurrent.atomic.AtomicLong;
       
    60 
       
    61 /**
       
    62  * @test
       
    63  * @bug 8232625
       
    64  * @summary This test verifies that the HttpClient works correctly when redirecting a post request.
       
    65  * @library /test/lib http2/server
       
    66  * @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters DigestEchoServer HttpRedirectTest
       
    67  * @modules java.net.http/jdk.internal.net.http.common
       
    68  *          java.net.http/jdk.internal.net.http.frame
       
    69  *          java.net.http/jdk.internal.net.http.hpack
       
    70  *          java.logging
       
    71  *          java.base/sun.net.www.http
       
    72  *          java.base/sun.net.www
       
    73  *          java.base/sun.net
       
    74  * @run testng/othervm -Dtest.requiresHost=true
       
    75  *                   -Djdk.httpclient.HttpClient.log=headers
       
    76  *                   -Djdk.internal.httpclient.debug=false
       
    77  *                   HttpRedirectTest
       
    78  *
       
    79  */
       
    80 public class HttpRedirectTest implements HttpServerAdapters {
       
    81     static final String GET_RESPONSE_BODY = "Lorem ipsum dolor sit amet";
       
    82     static final String REQUEST_BODY = "Here it goes";
       
    83     static final SSLContext context;
       
    84     static {
       
    85         try {
       
    86             context = new SimpleSSLContext().get();
       
    87             SSLContext.setDefault(context);
       
    88         } catch (Exception x) {
       
    89             throw new ExceptionInInitializerError(x);
       
    90         }
       
    91     }
       
    92 
       
    93     final AtomicLong requestCounter = new AtomicLong();
       
    94     final AtomicLong responseCounter = new AtomicLong();
       
    95     HttpTestServer http1Server;
       
    96     HttpTestServer http2Server;
       
    97     HttpTestServer https1Server;
       
    98     HttpTestServer https2Server;
       
    99     DigestEchoServer.TunnelingProxy proxy;
       
   100 
       
   101     URI http1URI;
       
   102     URI https1URI;
       
   103     URI http2URI;
       
   104     URI https2URI;
       
   105     InetSocketAddress proxyAddress;
       
   106     ProxySelector proxySelector;
       
   107     HttpClient client;
       
   108     List<CompletableFuture<?>>  futures = new CopyOnWriteArrayList<>();
       
   109     Set<URI> pending = new CopyOnWriteArraySet<>();
       
   110 
       
   111     final ExecutorService executor = new ThreadPoolExecutor(12, 60, 10,
       
   112             TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // Shared by HTTP/1.1 servers
       
   113     final ExecutorService clientexec = new ThreadPoolExecutor(6, 12, 1,
       
   114             TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // Used by the client
       
   115 
       
   116     public HttpClient newHttpClient(ProxySelector ps) {
       
   117         HttpClient.Builder builder = HttpClient
       
   118                 .newBuilder()
       
   119                 .sslContext(context)
       
   120                 .executor(clientexec)
       
   121                 .followRedirects(HttpClient.Redirect.ALWAYS)
       
   122                 .proxy(ps);
       
   123         return builder.build();
       
   124     }
       
   125 
       
   126     @DataProvider(name="uris")
       
   127     Object[][] testURIs() throws URISyntaxException {
       
   128         List<URI> uris = List.of(
       
   129                 http1URI.resolve("direct/orig/"),
       
   130                 https1URI.resolve("direct/orig/"),
       
   131                 https1URI.resolve("proxy/orig/"),
       
   132                 http2URI.resolve("direct/orig/"),
       
   133                 https2URI.resolve("direct/orig/"),
       
   134                 https2URI.resolve("proxy/orig/"));
       
   135         List<Map.Entry<Integer, String>> redirects = List.of(
       
   136                 Map.entry(301, "GET"),
       
   137                 Map.entry(308, "POST"),
       
   138                 Map.entry(302, "GET"),
       
   139                 Map.entry(303, "GET"),
       
   140                 Map.entry(307, "POST"),
       
   141                 Map.entry(300, "DO_NOT_FOLLOW"),
       
   142                 Map.entry(304, "DO_NOT_FOLLOW"),
       
   143                 Map.entry(305, "DO_NOT_FOLLOW"),
       
   144                 Map.entry(306, "DO_NOT_FOLLOW"),
       
   145                 Map.entry(309, "DO_NOT_FOLLOW"),
       
   146                 Map.entry(new Random().nextInt(90) + 310, "DO_NOT_FOLLOW")
       
   147         );
       
   148         Object[][] tests = new Object[redirects.size() * uris.size()][3];
       
   149         int count = 0;
       
   150         for (int i=0; i < uris.size(); i++) {
       
   151             URI u = uris.get(i);
       
   152             for (int j=0; j < redirects.size() ; j++) {
       
   153                 int code = redirects.get(j).getKey();
       
   154                 String m = redirects.get(j).getValue();
       
   155                 tests[count][0] = u.resolve(code +"/");
       
   156                 tests[count][1] = code;
       
   157                 tests[count][2] = m;
       
   158                 count++;
       
   159             }
       
   160         }
       
   161         return tests;
       
   162     }
       
   163 
       
   164     @BeforeClass
       
   165     public void setUp() throws Exception {
       
   166         try {
       
   167             InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
       
   168 
       
   169             // HTTP/1.1
       
   170             HttpServer server1 = HttpServer.create(sa, 0);
       
   171             server1.setExecutor(executor);
       
   172             http1Server = HttpTestServer.of(server1);
       
   173             http1Server.addHandler(new HttpTestRedirectHandler("http", http1Server),
       
   174                     "/HttpRedirectTest/http1/");
       
   175             http1Server.start();
       
   176             http1URI = new URI("http://" + http1Server.serverAuthority() + "/HttpRedirectTest/http1/");
       
   177 
       
   178 
       
   179             // HTTPS/1.1
       
   180             HttpsServer sserver1 = HttpsServer.create(sa, 100);
       
   181             sserver1.setExecutor(executor);
       
   182             sserver1.setHttpsConfigurator(new HttpsConfigurator(context));
       
   183             https1Server = HttpTestServer.of(sserver1);
       
   184             https1Server.addHandler(new HttpTestRedirectHandler("https", https1Server),
       
   185                     "/HttpRedirectTest/https1/");
       
   186             https1Server.start();
       
   187             https1URI = new URI("https://" + https1Server.serverAuthority() + "/HttpRedirectTest/https1/");
       
   188 
       
   189             // HTTP/2.0
       
   190             http2Server = HttpTestServer.of(
       
   191                     new Http2TestServer("localhost", false, 0));
       
   192             http2Server.addHandler(new HttpTestRedirectHandler("http", http2Server),
       
   193                     "/HttpRedirectTest/http2/");
       
   194             http2Server.start();
       
   195             http2URI = new URI("http://" + http2Server.serverAuthority() + "/HttpRedirectTest/http2/");
       
   196 
       
   197             // HTTPS/2.0
       
   198             https2Server = HttpTestServer.of(
       
   199                     new Http2TestServer("localhost", true, 0));
       
   200             https2Server.addHandler(new HttpTestRedirectHandler("https", https2Server),
       
   201                     "/HttpRedirectTest/https2/");
       
   202             https2Server.start();
       
   203             https2URI = new URI("https://" + https2Server.serverAuthority() + "/HttpRedirectTest/https2/");
       
   204 
       
   205             proxy = DigestEchoServer.createHttpsProxyTunnel(
       
   206                     DigestEchoServer.HttpAuthSchemeType.NONE);
       
   207             proxyAddress = proxy.getProxyAddress();
       
   208             proxySelector = new HttpProxySelector(proxyAddress);
       
   209             client = newHttpClient(proxySelector);
       
   210             System.out.println("Setup: done");
       
   211         } catch (Exception x) {
       
   212             tearDown(); throw x;
       
   213         } catch (Error e) {
       
   214             tearDown(); throw e;
       
   215         }
       
   216     }
       
   217 
       
   218     private void testNonIdempotent(URI u, HttpRequest request,
       
   219                                    int code, String method) throws Exception {
       
   220         System.out.println("Testing with " + u);
       
   221         CompletableFuture<HttpResponse<String>> respCf =
       
   222                 client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
       
   223         HttpResponse<String> resp = respCf.join();
       
   224         if (method.equals("DO_NOT_FOLLOW")) {
       
   225             assertEquals(resp.statusCode(), code, u + ": status code");
       
   226         } else {
       
   227             assertEquals(resp.statusCode(), 200, u + ": status code");
       
   228         }
       
   229         if (method.equals("POST")) {
       
   230             assertEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   231         } else if (code == 304) {
       
   232             assertEquals(resp.body(), "", u + ": body");
       
   233         } else if (method.equals("DO_NOT_FOLLOW")) {
       
   234             assertNotEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   235             assertNotEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   236         } else {
       
   237             assertEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   238         }
       
   239     }
       
   240 
       
   241     public void testIdempotent(URI u, HttpRequest request,
       
   242                                int code, String method) throws Exception {
       
   243         CompletableFuture<HttpResponse<String>> respCf =
       
   244                 client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
       
   245         HttpResponse<String> resp = respCf.join();
       
   246         if (method.equals("DO_NOT_FOLLOW")) {
       
   247             assertEquals(resp.statusCode(), code, u + ": status code");
       
   248         } else {
       
   249             assertEquals(resp.statusCode(), 200, u + ": status code");
       
   250         }
       
   251         if (method.equals("POST")) {
       
   252             assertEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   253         } else if (code == 304) {
       
   254             assertEquals(resp.body(), "", u + ": body");
       
   255         } else if (method.equals("DO_NOT_FOLLOW")) {
       
   256             assertNotEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   257             assertNotEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   258         } else if (code == 303) {
       
   259             assertEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   260         } else {
       
   261             assertEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   262         }
       
   263     }
       
   264 
       
   265     @Test(dataProvider = "uris")
       
   266     public void testPOST(URI uri, int code, String method) throws Exception {
       
   267         URI u = uri.resolve("foo?n=" + requestCounter.incrementAndGet());
       
   268         HttpRequest request = HttpRequest.newBuilder(u)
       
   269                 .POST(HttpRequest.BodyPublishers.ofString(REQUEST_BODY)).build();
       
   270         // POST is not considered idempotent.
       
   271         testNonIdempotent(u, request, code, method);
       
   272     }
       
   273 
       
   274     @Test(dataProvider = "uris")
       
   275     public void testPUT(URI uri, int code, String method) throws Exception {
       
   276         URI u = uri.resolve("foo?n=" + requestCounter.incrementAndGet());
       
   277         System.out.println("Testing with " + u);
       
   278         HttpRequest request = HttpRequest.newBuilder(u)
       
   279                 .PUT(HttpRequest.BodyPublishers.ofString(REQUEST_BODY)).build();
       
   280         // PUT is considered idempotent.
       
   281         testIdempotent(u, request, code, method);
       
   282     }
       
   283 
       
   284     @Test(dataProvider = "uris")
       
   285     public void testFoo(URI uri, int code, String method) throws Exception {
       
   286         URI u = uri.resolve("foo?n=" + requestCounter.incrementAndGet());
       
   287         System.out.println("Testing with " + u);
       
   288         HttpRequest request = HttpRequest.newBuilder(u)
       
   289                 .method("FOO",
       
   290                         HttpRequest.BodyPublishers.ofString(REQUEST_BODY)).build();
       
   291         // FOO is considered idempotent.
       
   292         testIdempotent(u, request, code, method);
       
   293     }
       
   294 
       
   295     @Test(dataProvider = "uris")
       
   296     public void testGet(URI uri, int code, String method) throws Exception {
       
   297         URI u = uri.resolve("foo?n=" + requestCounter.incrementAndGet());
       
   298         System.out.println("Testing with " + u);
       
   299         HttpRequest request = HttpRequest.newBuilder(u)
       
   300                 .method("GET",
       
   301                         HttpRequest.BodyPublishers.ofString(REQUEST_BODY)).build();
       
   302         CompletableFuture<HttpResponse<String>> respCf =
       
   303                 client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
       
   304         HttpResponse<String> resp = respCf.join();
       
   305         // body will be preserved except for 304 and 303: this is a GET.
       
   306         if (method.equals("DO_NOT_FOLLOW")) {
       
   307             assertEquals(resp.statusCode(), code, u + ": status code");
       
   308         } else {
       
   309             assertEquals(resp.statusCode(), 200, u + ": status code");
       
   310         }
       
   311         if (code == 304) {
       
   312             assertEquals(resp.body(), "", u + ": body");
       
   313         } else if (method.equals("DO_NOT_FOLLOW")) {
       
   314             assertNotEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   315             assertNotEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   316         } else if (code == 303) {
       
   317             assertEquals(resp.body(), GET_RESPONSE_BODY, u + ": body");
       
   318         } else {
       
   319             assertEquals(resp.body(), REQUEST_BODY, u + ": body");
       
   320         }
       
   321     }
       
   322 
       
   323     @AfterClass
       
   324     public void tearDown() {
       
   325         proxy = stop(proxy, DigestEchoServer.TunnelingProxy::stop);
       
   326         http1Server = stop(http1Server, HttpTestServer::stop);
       
   327         https1Server = stop(https1Server, HttpTestServer::stop);
       
   328         http2Server = stop(http2Server, HttpTestServer::stop);
       
   329         https2Server = stop(https2Server, HttpTestServer::stop);
       
   330         client = null;
       
   331         try {
       
   332             executor.awaitTermination(2000, TimeUnit.MILLISECONDS);
       
   333         } catch (Throwable x) {
       
   334         } finally {
       
   335             executor.shutdownNow();
       
   336         }
       
   337         try {
       
   338             clientexec.awaitTermination(2000, TimeUnit.MILLISECONDS);
       
   339         } catch (Throwable x) {
       
   340         } finally {
       
   341             clientexec.shutdownNow();
       
   342         }
       
   343         System.out.println("Teardown: done");
       
   344     }
       
   345 
       
   346     private interface Stoppable<T> { public void stop(T service) throws Exception; }
       
   347 
       
   348     static <T>  T stop(T service, Stoppable<T> stop) {
       
   349         try { if (service != null) stop.stop(service); } catch (Throwable x) { };
       
   350         return null;
       
   351     }
       
   352 
       
   353     static class HttpProxySelector extends ProxySelector {
       
   354         private static final List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
       
   355         private final List<Proxy> proxyList;
       
   356         HttpProxySelector(InetSocketAddress proxyAddress) {
       
   357             proxyList = List.of(new Proxy(Proxy.Type.HTTP, proxyAddress));
       
   358         }
       
   359 
       
   360         @Override
       
   361         public List<Proxy> select(URI uri) {
       
   362             // our proxy only supports tunneling
       
   363             if (uri.getScheme().equalsIgnoreCase("https")) {
       
   364                 if (uri.getPath().contains("/proxy/")) {
       
   365                     return proxyList;
       
   366                 }
       
   367             }
       
   368             return NO_PROXY;
       
   369         }
       
   370 
       
   371         @Override
       
   372         public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
       
   373             System.err.println("Connection to proxy failed: " + ioe);
       
   374             System.err.println("Proxy: " + sa);
       
   375             System.err.println("\tURI: " + uri);
       
   376             ioe.printStackTrace();
       
   377         }
       
   378     }
       
   379 
       
   380     public static class HttpTestRedirectHandler implements HttpTestHandler {
       
   381         static final AtomicLong respCounter = new AtomicLong();
       
   382         final String scheme;
       
   383         final HttpTestServer server;
       
   384         HttpTestRedirectHandler(String scheme, HttpTestServer server) {
       
   385             this.scheme = scheme;
       
   386             this.server = server;
       
   387         }
       
   388 
       
   389         @Override
       
   390         public void handle(HttpTestExchange t) throws IOException {
       
   391             try (InputStream is = t.getRequestBody()) {
       
   392                 byte[] bytes = is.readAllBytes();
       
   393                 URI u = t.getRequestURI();
       
   394                 long responseID = Long.parseLong(u.getQuery().substring(2));
       
   395                 String path = u.getPath();
       
   396                 int i = path.lastIndexOf('/');
       
   397                 String file = path.substring(i+1);
       
   398                 String parent =  path.substring(0, i);
       
   399                 int code = 200;
       
   400                 if (file.equals("foo")) {
       
   401                     i = parent.lastIndexOf("/");
       
   402                     code = Integer.parseInt(parent.substring(i+1));
       
   403                 }
       
   404                 String response;
       
   405                 if (code == 200) {
       
   406                     if (t.getRequestMethod().equals("GET")) {
       
   407                         if (bytes.length == 0) {
       
   408                             response = GET_RESPONSE_BODY;
       
   409                         } else {
       
   410                             response = new String(bytes, StandardCharsets.UTF_8);
       
   411                         }
       
   412                     } else if (t.getRequestMethod().equals("POST")) {
       
   413                         response = new String(bytes, StandardCharsets.UTF_8);
       
   414                     } else {
       
   415                         response = new String(bytes, StandardCharsets.UTF_8);
       
   416                     }
       
   417                 } else if (code < 300 || code > 399) {
       
   418                     response = "Unexpected code: " + code;
       
   419                     code = 400;
       
   420                 } else {
       
   421                     try {
       
   422                         URI reloc = new URI(scheme, server.serverAuthority(), parent + "/bar", u.getQuery(), null);
       
   423                         t.getResponseHeaders().addHeader("Location", reloc.toASCIIString());
       
   424                         if (code != 304) {
       
   425                             response = "Code: " + code;
       
   426                         } else response = null;
       
   427                     } catch (URISyntaxException x) {
       
   428                         x.printStackTrace();
       
   429                         x.printStackTrace(System.out);
       
   430                         code = 400;
       
   431                         response = x.toString();
       
   432                     }
       
   433                 }
       
   434 
       
   435                 System.out.println("Server " + t.getRequestURI() + " sending response " + responseID);
       
   436                 System.out.println("code: " + code + " body: " + response);
       
   437                 t.sendResponseHeaders(code, code == 304 ? 0: -1);
       
   438                 if (code != 304) {
       
   439                     try (OutputStream os = t.getResponseBody()) {
       
   440                         bytes = response.getBytes(StandardCharsets.UTF_8);
       
   441                         os.write(bytes);
       
   442                         os.flush();
       
   443                     }
       
   444                 } else {
       
   445                     bytes = new byte[0];
       
   446                 }
       
   447 
       
   448                 System.out.println("\tresp:" + responseID + ": wrote " + bytes.length + " bytes");
       
   449             } catch (Throwable e) {
       
   450                 e.printStackTrace();
       
   451                 e.printStackTrace(System.out);
       
   452                 throw e;
       
   453             }
       
   454         }
       
   455     }
       
   456 
       
   457 }