test/jdk/java/net/httpclient/RedirectMethodChange.java
author chegar
Wed, 20 Jun 2018 09:05:57 -0700
changeset 50681 4254bed3c09d
parent 49765 ee6f7a61f3a5
child 52121 934969c63223
child 56795 03ece2518428
permissions -rw-r--r--
8204679: HTTP Client refresh Reviewed-by: chegar, dfuchs, michaelm Contributed-by: Chris Hegarty <chris.hegarty@oracle.com>, Daniel Fuchs <daniel.fuchs@oracle.com>, Michael McMahon <michael.x.mcmahon@oracle.com>, Pavel Rappo <pavel.rappo@oracle.com>

/*
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @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
 * @library /lib/testlibrary /test/lib http2/server
 * @build Http2TestServer
 * @build jdk.testlibrary.SimpleSSLContext
 * @run testng/othervm RedirectMethodChange
 */

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
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 implements HttpServerAdapters {

    SSLContext sslContext;
    HttpClient client;

    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 HttpRequest.BodyPublisher getRequestBodyFor(String method) {
        switch (method) {
            case "GET":
            case "DELETE":
            case "HEAD":
                return BodyPublishers.noBody();
            case "POST":
            case "PUT":
                return BodyPublishers.ofString(POST_BODY);
            default:
                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, sslContext));
        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();
    }

    /**
     * Stateful handler.
     *
     * 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 RedirMethodChgeHandler implements HttpTestHandler {

        boolean inTest;
        String expectedMethod;

        final String targetURL;
        RedirMethodChgeHandler(String targetURL) {
            this.targetURL = targetURL;
        }

        boolean readAndCheckBody(HttpTestExchange e) throws IOException {
            String method = e.getRequestMethod();
            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 synchronized void handle(HttpTestExchange he) throws IOException {
            boolean newtest = he.getRequestURI().getPath().endsWith("/test/rmt");
            if ((newtest && inTest) || (!newtest && !inTest)) {
                Throwable ex = new RuntimeException("Unexpected newtest:" + newtest
                        + ", inTest:" + inTest +  ", for " + he.getRequestURI());
                ex.printStackTrace();
                he.sendResponseHeaders(500, 0);
                return;
            }

            if (newtest) {
                HttpTestRequestHeaders 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;
                HttpTestResponseHeaders headersbuilder = he.getResponseHeaders();
                headersbuilder.addHeader("Location", targetURL);
                he.sendResponseHeaders(redirectCode, 0);
                inTest = true;
            } else {
                // should be the redirect
                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)) {
                    Throwable ex = new RuntimeException("Expected: " + expectedMethod
                            + " Got: " + he.getRequestMethod());
                    ex.printStackTrace();
                    he.sendResponseHeaders(504, 0);
                } else {
                    if (!readAndCheckBody(he))
                        return;
                    he.sendResponseHeaders(200, RESPONSE.length());
                    try (OutputStream os = he.getResponseBody()) {
                        os.write(RESPONSE.getBytes(US_ASCII));
                    }
                }
                inTest = false;
            }
        }
    }
}