http-client-branch: expand RedirectMethodChange test to cover HTTP2 and HTTPS http-client-branch
authorchegar
Tue, 13 Mar 2018 14:16:52 +0000
branchhttp-client-branch
changeset 56289 904b7c299931
parent 56288 2de1aa88cf06
child 56296 829b4c16ac31
http-client-branch: expand RedirectMethodChange test to cover HTTP2 and HTTPS
src/java.net.http/share/classes/java/net/http/HttpClient.java
src/java.net.http/share/classes/java/net/http/HttpRequest.java
src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java
test/jdk/java/net/httpclient/RedirectMethodChange.java
--- a/src/java.net.http/share/classes/java/net/http/HttpClient.java	Tue Mar 13 14:24:15 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpClient.java	Tue Mar 13 14:16:52 2018 +0000
@@ -83,7 +83,7 @@
  * <p><b>Synchronous Example</b>
  * <pre>{@code    HttpClient client = HttpClient.newBuilder()
  *        .version(Version.HTTP_1_1)
- *        .followRedirects(Redirect.SAME_PROTOCOL)
+ *        .followRedirects(Redirect.NORMAL)
  *        .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
  *        .authenticator(Authenticator.getDefault())
  *        .build();
@@ -454,11 +454,13 @@
      * <p> {@code Redirect} policy is set via the {@linkplain
      * HttpClient.Builder#followRedirects(Redirect) Builder.followRedirects}
      * method.
-     * <p>
+     *
      * @implNote When automatic redirection occurs, the request method of the
      * redirected request may be modified depending on the specific {@code 30X}
-     * status code, as specified in RFC 7231. In addition, the 301 and 302 status
-     * codes, cause a POST request to be converted to a GET in the redirected request.
+     * status code, as specified in <a href="https://tools.ietf.org/html/rfc7231">
+     * RFC 7231</a>. In addition, the {@code 301} and {@code 302} status codes,
+     * cause a {@code POST} request to be converted to a {@code GET} in the
+     * redirected request.
      *
      * @since 11
      */
--- a/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Tue Mar 13 14:24:15 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Tue Mar 13 14:16:52 2018 +0000
@@ -51,8 +51,8 @@
  * methods. A request's {@link URI}, headers, and body can be set. Request
  * bodies are provided through a {@link BodyPublisher BodyPublisher} supplied
  * to one of the {@link Builder#POST(BodyPublisher) POST},
- * {@link Builder#PUT(BodyPublisher) PUT} or {@link Builder#method(String,BodyPublisher)
- * method} methods.
+ * {@link Builder#PUT(BodyPublisher) PUT} or
+ * {@link Builder#method(String,BodyPublisher) method} methods.
  * Once all required parameters have been set in the builder, {@link
  * Builder#build() build} will return the {@code HttpRequest}. Builders can be
  * copied and modified many times in order to build multiple related requests
@@ -246,7 +246,6 @@
          *
          * @return this builder
          */
-
         public Builder DELETE();
 
         /**
--- a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java	Tue Mar 13 14:24:15 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java	Tue Mar 13 14:16:52 2018 +0000
@@ -66,7 +66,7 @@
         return handleResponse(r);
     }
 
-    private String redirectedMethod(int statusCode, String orig) {
+    private static String redirectedMethod(int statusCode, String orig) {
         switch (statusCode) {
             case 301:
             case 302:
--- a/test/jdk/java/net/httpclient/RedirectMethodChange.java	Tue Mar 13 14:24:15 2018 +0000
+++ b/test/jdk/java/net/httpclient/RedirectMethodChange.java	Tue Mar 13 14:16:52 2018 +0000
@@ -21,19 +21,21 @@
  * questions.
  */
 
-/**
+/*
  * @test
- * @bug 8087112
- * @modules java.net.http
+ * @summary Method change during redirection
+ * @modules java.base/sun.net.www.http
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          jdk.httpserver
- * @run main/othervm RedirectMethodChange
+ * @library /lib/testlibrary /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm RedirectMethodChange
  */
 
-import com.sun.net.httpserver.HttpContext;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.Headers;
+import javax.net.ssl.SSLContext;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -45,154 +47,270 @@
 import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.logging.*;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.testng.Assert.assertEquals;
 
-public class RedirectMethodChange {
+public class RedirectMethodChange implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpClient client;
 
-    static volatile boolean ok;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
     static final String RESPONSE = "Hello world";
     static final String POST_BODY = "This is the POST body 123909090909090";
-    static volatile URI TEST_URI, REDIRECT_URI;
-    static volatile HttpClient client;
-
-    public static void main(String[] args) throws Exception {
-        //Logger l = Logger.getLogger("com.sun.net.httpserver");
-        //l.setLevel(Level.ALL);
-        //ConsoleHandler ch = new ConsoleHandler();
-        //ch.setLevel(Level.ALL);
-        //l.addHandler(ch);
-
-        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(),0);
-        HttpServer server = HttpServer.create(addr, 10);
-        ExecutorService e = Executors.newCachedThreadPool();
-        Handler h = new Handler();
-        HttpContext serverContext = server.createContext("/test/", h);
-        HttpContext serverContext1 = server.createContext("/redirect/", h);
-        int port = server.getAddress().getPort();
-        System.out.println("Server address = " + server.getAddress());
-
-        server.setExecutor(e);
-        server.start();
-        client = HttpClient.newBuilder()
-                      .followRedirects(HttpClient.Redirect.NORMAL)
-                      .build();
-
-        try {
-            TEST_URI = new URI("http://localhost:" + Integer.toString(port) + "/test/foo");
-            REDIRECT_URI = new URI("http://localhost:" + Integer.toString(port) + "/redirect/foo");
-            test("GET", 301, "GET");
-            test("GET", 302, "GET");
-            test("GET", 303, "GET");
-            test("GET", 307, "GET");
-            test("GET", 308, "GET");
-            test("POST", 301, "GET");
-            test("POST", 302, "GET");
-            test("POST", 303, "GET");
-            test("POST", 307, "POST");
-            test("POST", 308, "POST");
-            test("PUT", 301, "PUT");
-            test("PUT", 302, "PUT");
-            test("PUT", 303, "GET");
-            test("PUT", 307, "PUT");
-            test("PUT", 308, "PUT");
-        } finally {
-            server.stop(0);
-            e.shutdownNow();
-        }
-        System.out.println("OK");
-    }
 
     static HttpRequest.BodyPublisher getRequestBodyFor(String method) {
         switch (method) {
             case "GET":
             case "DELETE":
             case "HEAD":
-                return HttpRequest.BodyPublishers.noBody();
+                return BodyPublishers.noBody();
             case "POST":
             case "PUT":
-                return HttpRequest.BodyPublishers.ofString(POST_BODY);
+                return BodyPublishers.ofString(POST_BODY);
             default:
-                throw new InternalError();
-        }
-    }
-
-    static void test(String method, int redirectCode, String expectedMethod) throws Exception {
-        System.err.printf("Test %s, %d, %s %s\n", method, redirectCode, expectedMethod, TEST_URI.toString());
-        HttpRequest req = HttpRequest.newBuilder(TEST_URI)
-            .method(method, getRequestBodyFor(method))
-            .header("X-Redirect-Code", Integer.toString(redirectCode))
-            .header("X-Expect-Method", expectedMethod)
-            .build();
-        HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
-
-        if (resp.statusCode() != 200 || !resp.body().equals(RESPONSE)) {
-            String msg = "Failed: " + resp.statusCode();
-            throw new RuntimeException(msg);
+                throw new AssertionError("Unknown method:" + method);
         }
     }
 
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][] {
+                { httpURI, "GET",  301, "GET"  },
+                { httpURI, "GET",  302, "GET"  },
+                { httpURI, "GET",  303, "GET"  },
+                { httpURI, "GET",  307, "GET"  },
+                { httpURI, "GET",  308, "GET"  },
+                { httpURI, "POST", 301, "GET"  },
+                { httpURI, "POST", 302, "GET"  },
+                { httpURI, "POST", 303, "GET"  },
+                { httpURI, "POST", 307, "POST" },
+                { httpURI, "POST", 308, "POST" },
+                { httpURI, "PUT",  301, "PUT"  },
+                { httpURI, "PUT",  302, "PUT"  },
+                { httpURI, "PUT",  303, "GET"  },
+                { httpURI, "PUT",  307, "PUT"  },
+                { httpURI, "PUT",  308, "PUT"  },
+
+                { httpsURI, "GET",  301, "GET"  },
+                { httpsURI, "GET",  302, "GET"  },
+                { httpsURI, "GET",  303, "GET"  },
+                { httpsURI, "GET",  307, "GET"  },
+                { httpsURI, "GET",  308, "GET"  },
+                { httpsURI, "POST", 301, "GET"  },
+                { httpsURI, "POST", 302, "GET"  },
+                { httpsURI, "POST", 303, "GET"  },
+                { httpsURI, "POST", 307, "POST" },
+                { httpsURI, "POST", 308, "POST" },
+                { httpsURI, "PUT",  301, "PUT"  },
+                { httpsURI, "PUT",  302, "PUT"  },
+                { httpsURI, "PUT",  303, "GET"  },
+                { httpsURI, "PUT",  307, "PUT"  },
+                { httpsURI, "PUT",  308, "PUT"  },
+
+                { http2URI, "GET",  301, "GET"  },
+                { http2URI, "GET",  302, "GET"  },
+                { http2URI, "GET",  303, "GET"  },
+                { http2URI, "GET",  307, "GET"  },
+                { http2URI, "GET",  308, "GET"  },
+                { http2URI, "POST", 301, "GET"  },
+                { http2URI, "POST", 302, "GET"  },
+                { http2URI, "POST", 303, "GET"  },
+                { http2URI, "POST", 307, "POST" },
+                { http2URI, "POST", 308, "POST" },
+                { http2URI, "PUT",  301, "PUT"  },
+                { http2URI, "PUT",  302, "PUT"  },
+                { http2URI, "PUT",  303, "GET"  },
+                { http2URI, "PUT",  307, "PUT"  },
+                { http2URI, "PUT",  308, "PUT"  },
+
+                { https2URI, "GET",  301, "GET"  },
+                { https2URI, "GET",  302, "GET"  },
+                { https2URI, "GET",  303, "GET"  },
+                { https2URI, "GET",  307, "GET"  },
+                { https2URI, "GET",  308, "GET"  },
+                { https2URI, "POST", 301, "GET"  },
+                { https2URI, "POST", 302, "GET"  },
+                { https2URI, "POST", 303, "GET"  },
+                { https2URI, "POST", 307, "POST" },
+                { https2URI, "POST", 308, "POST" },
+                { https2URI, "PUT",  301, "PUT"  },
+                { https2URI, "PUT",  302, "PUT"  },
+                { https2URI, "PUT",  303, "GET"  },
+                { https2URI, "PUT",  307, "PUT"  },
+                { https2URI, "PUT",  308, "PUT"  },
+        };
+    }
+
+    @Test(dataProvider = "variants")
+    public void test(String uriString,
+                     String method,
+                     int redirectCode,
+                     String expectedMethod)
+        throws Exception
+    {
+        HttpRequest req = HttpRequest.newBuilder(URI.create(uriString))
+                .method(method, getRequestBodyFor(method))
+                .header("X-Redirect-Code", Integer.toString(redirectCode))
+                .header("X-Expect-Method", expectedMethod)
+                .build();
+        HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
+
+        System.out.println("Response: " + resp + ", body: " + resp.body());
+        assertEquals(resp.statusCode(), 200);
+        assertEquals(resp.body(), RESPONSE);
+    }
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        client = HttpClient.newBuilder()
+                .followRedirects(HttpClient.Redirect.NORMAL)
+                .sslContext(sslContext)
+                .build();
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        String targetURI = "http://" + httpTestServer.serverAuthority() + "/http1/redirect/rmt";
+        RedirMethodChgeHandler handler = new RedirMethodChgeHandler(targetURI);
+        httpTestServer.addHandler(handler, "/http1/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/test/rmt";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        targetURI = "https://" + httpsTestServer.serverAuthority() + "/https1/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        httpsTestServer.addHandler(handler,"/https1/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/test/rmt";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        targetURI = "http://" + http2TestServer.serverAuthority() + "/http2/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        http2TestServer.addHandler(handler, "/http2/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/test/rmt";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        targetURI = "https://" + https2TestServer.serverAuthority() + "/https2/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        https2TestServer.addHandler(handler, "/https2/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/test/rmt";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
     /**
-     * request to /test is first test. The following headers are checked:
-     * X-Redirect-Code: nnn    <the redirect code to send back>
-     * X-Expect-Method: the method that the client should use for the next request
+     * Stateful handler.
      *
-     * Following request should be to /redirect and should use the method indicated
-     * previously. If all ok, return a 200 response. Otherwise 500 error.
+     * Request to "<protocol>/test/rmt" is first, with the following checked
+     * headers:
+     *   X-Redirect-Code: nnn    <the redirect code to send back>
+     *   X-Expect-Method: the method that the client should use for the next request
+     *
+     * The following request should be to "<protocol>/redirect/rmt" and should
+     * use the method indicated previously. If all ok, return a 200 response.
+     * Otherwise 50X error.
      */
-    static class Handler implements HttpHandler {
+    static class RedirMethodChgeHandler implements HttpTestHandler {
 
-        volatile boolean inTest = false;
+        volatile boolean inTest;
         volatile String expectedMethod;
 
-        boolean readAndCheckBody(HttpExchange e) throws IOException {
-            InputStream is = e.getRequestBody();
+        final String targetURL;
+        RedirMethodChgeHandler(String targetURL) {
+            this.targetURL = targetURL;
+        }
+
+        boolean readAndCheckBody(HttpTestExchange e) throws IOException {
             String method = e.getRequestMethod();
-            String requestBody = new String(is.readAllBytes(), US_ASCII);
-            is.close();
-            if (method.equals("POST") || method.equals("PUT")) {
-                if (!requestBody.equals(POST_BODY)) {
-                    e.sendResponseHeaders(503, -1);
-                    return false;
-                }
+            String requestBody;
+            try (InputStream is = e.getRequestBody()) {
+                requestBody = new String(is.readAllBytes(), US_ASCII);
+            }
+            if ((method.equals("POST") || method.equals("PUT"))
+                    && !requestBody.equals(POST_BODY)) {
+                Throwable ex = new RuntimeException("Unexpected request body for "
+                        + method + ": [" + requestBody +"]");
+                ex.printStackTrace();
+                e.sendResponseHeaders(503, 0);
+                return false;
             }
             return true;
         }
 
         @Override
-        public void handle(HttpExchange he) throws IOException {
-            boolean newtest = he.getRequestURI().getPath().startsWith("/test");
+        public void handle(HttpTestExchange he) throws IOException {
+            boolean newtest = he.getRequestURI().getPath().endsWith("/test/rmt");
             if ((newtest && inTest) || (!newtest && !inTest)) {
-                he.sendResponseHeaders(500, -1);
+                Throwable ex = new RuntimeException("Unexpected newtest:" + newtest
+                        + ", inTest:" + inTest +  ", for " + he.getRequestURI());
+                ex.printStackTrace();
+                he.sendResponseHeaders(500, 0);
+                return;
             }
+
             if (newtest) {
-                String method = he.getRequestMethod();
-                Headers hdrs = he.getRequestHeaders();
-                int redirectCode = Integer.parseInt(hdrs.getFirst("X-Redirect-Code"));
-                expectedMethod = hdrs.getFirst("X-Expect-Method");
-                boolean ok = readAndCheckBody(he);
-                if (!ok)
+                HttpTestHeaders hdrs = he.getRequestHeaders();
+                String value = hdrs.firstValue("X-Redirect-Code").get();
+                int redirectCode = Integer.parseInt(value);
+                expectedMethod = hdrs.firstValue("X-Expect-Method").get();
+                if (!readAndCheckBody(he))
                     return;
                 hdrs = he.getResponseHeaders();
-                hdrs.set("Location", REDIRECT_URI.toString());
-                he.sendResponseHeaders(redirectCode, -1);
+                hdrs.addHeader("Location", targetURL);
+                he.sendResponseHeaders(redirectCode, 0);
                 inTest = true;
             } else {
                 // should be the redirect
-                if (!he.getRequestURI().getPath().startsWith("/redirect")) {
-                    he.sendResponseHeaders(501, -1);
+                if (!he.getRequestURI().getPath().endsWith("/redirect/rmt")) {
+                    Throwable ex = new RuntimeException("Unexpected redirected request, got:"
+                            + he.getRequestURI());
+                    ex.printStackTrace();
+                    he.sendResponseHeaders(501, 0);
                 } else if (!he.getRequestMethod().equals(expectedMethod)) {
-                    System.err.println("Expected: " + expectedMethod + " Got: " + he.getRequestMethod());
-                    he.sendResponseHeaders(504, -1);
+                    Throwable ex = new RuntimeException("Expected: " + expectedMethod
+                            + " Got: " + he.getRequestMethod());
+                    ex.printStackTrace();
+                    he.sendResponseHeaders(504, 0);
                 } else {
-                    boolean ok = readAndCheckBody(he);
-                    if (ok) {
-                        he.sendResponseHeaders(200, RESPONSE.length());
-                        OutputStream os = he.getResponseBody();
+                    if (!readAndCheckBody(he))
+                        return;
+                    he.sendResponseHeaders(200, RESPONSE.length());
+                    try (OutputStream os = he.getResponseBody()) {
                         os.write(RESPONSE.getBytes(US_ASCII));
-                        os.close();
                     }
                 }
                 inTest = false;