Path: blob/master/test/jdk/sun/net/www/http/HttpClient/B8209178.java
41154 views
/*1* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 820917826* @modules java.base/sun.net.www java.base/sun.security.x509 java.base/sun.security.tools.keytool27* @library /test/lib28* @run main/othervm -Dsun.net.http.retryPost=true B820917829* @run main/othervm -Dsun.net.http.retryPost=false B820917830* @summary Proxied HttpsURLConnection doesn't send BODY when retrying POST request31*/3233import java.io.*;34import java.net.*;35import java.nio.charset.StandardCharsets;36import java.security.KeyStore;37import java.security.NoSuchAlgorithmException;38import java.security.cert.X509Certificate;39import java.util.HashMap;40import javax.net.ssl.*;4142import com.sun.net.httpserver.*;43import jdk.test.lib.net.URIBuilder;44import sun.security.tools.keytool.CertAndKeyGen;45import sun.security.x509.X500Name;4647public class B8209178 {48static {49try {50HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);51SSLContext.setDefault(new TestSSLContext().get());52} catch (Exception ex) {53throw new ExceptionInInitializerError(ex);54}55}5657static final String RESPONSE = "<html><body><p>Hello World!</body></html>";58static final String PATH = "/foo/";59static final String RETRYPOST = System.getProperty("sun.net.http.retryPost");6061static HttpServer createHttpsServer() throws IOException, NoSuchAlgorithmException {62HttpsServer server = HttpsServer.create();63HttpContext context = server.createContext(PATH);64context.setHandler(new HttpHandler() {6566boolean simulateError = true;6768@Override69public void handle(HttpExchange he) throws IOException {7071System.out.printf("%s - received request on : %s%n",72Thread.currentThread().getName(),73he.getRequestURI());74System.out.printf("%s - received request headers : %s%n",75Thread.currentThread().getName(),76new HashMap(he.getRequestHeaders()));7778InputStream requestBody = he.getRequestBody();79String body = B8209178.toString(requestBody);8081System.out.printf("%s - received request body : %s%n",82Thread.currentThread().getName(), body);8384if (simulateError) {85simulateError = false;8687System.out.printf("%s - closing connection unexpectedly ... %n",88Thread.currentThread().getName(), he.getRequestHeaders());8990he.close(); // try not to respond anything the first time ...91return;92}9394he.getResponseHeaders().add("encoding", "UTF-8");95he.sendResponseHeaders(200, RESPONSE.length());96he.getResponseBody().write(RESPONSE.getBytes(StandardCharsets.UTF_8));97he.close();98}99});100101server.setHttpsConfigurator(new Configurator(SSLContext.getDefault()));102server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);103return server;104}105106public static void main(String[] args) throws IOException, NoSuchAlgorithmException {107HttpServer server = createHttpsServer();108server.start();109try {110new B8209178().test(server);111112} finally {113server.stop(0);114System.out.println("Server stopped");115}116}117118public void test(HttpServer server /*, HttpClient.Version version*/) throws IOException {119System.out.println("System property retryPost: " + RETRYPOST);120System.out.println("Server is: " + server.getAddress());121System.out.println("Verifying communication with server");122URI uri = URIBuilder.newBuilder()123.scheme("https")124.host(server.getAddress().getAddress())125.port(server.getAddress().getPort())126.path(PATH + "x")127.buildUnchecked();128129TunnelingProxy proxy = new TunnelingProxy(server);130proxy.start();131132try {133System.out.println("Proxy started");134Proxy p = new Proxy(Proxy.Type.HTTP,135InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));136System.out.println("Verifying communication with proxy");137138callHttpsServerThroughProxy(uri, p);139140} finally {141System.out.println("Stopping proxy");142proxy.stop();143System.out.println("Proxy stopped");144}145}146147private void callHttpsServerThroughProxy(URI uri, Proxy p) throws IOException {148HttpsURLConnection urlConnection = (HttpsURLConnection) uri.toURL().openConnection(p);149150urlConnection.setConnectTimeout(1000);151urlConnection.setReadTimeout(3000);152urlConnection.setDoInput(true);153urlConnection.setDoOutput(true);154urlConnection.setRequestMethod("POST");155urlConnection.setUseCaches(false);156157urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");158urlConnection.setRequestProperty("charset", "utf-8");159urlConnection.setRequestProperty("Connection", "keep-alive");160161String urlParameters = "param1=a¶m2=b¶m3=c";162byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);163164OutputStream outputStream = urlConnection.getOutputStream();165outputStream.write(postData);166outputStream.close();167168int responseCode;169170try {171responseCode = urlConnection.getResponseCode();172System.out.printf(" ResponseCode : %s%n", responseCode);173String output;174InputStream inputStream = (responseCode < 400) ? urlConnection.getInputStream() : urlConnection.getErrorStream();175output = toString(inputStream);176inputStream.close();177System.out.printf(" Output from server : %s%n", output);178179if (responseCode == 200) { // OK !180} else {181throw new RuntimeException("Bad response Code : " + responseCode);182}183} catch (SocketException se) {184if (RETRYPOST.equals("true")) { // Should not get here with the fix185throw new RuntimeException("Unexpected Socket Exception: " + se);186} else {187System.out.println("Socket Exception received as expected: " + se);188}189}190}191192static class TunnelingProxy {193final Thread accept;194final ServerSocket ss;195final boolean DEBUG = false;196final HttpServer serverImpl;197198TunnelingProxy(HttpServer serverImpl) throws IOException {199this.serverImpl = serverImpl;200ss = new ServerSocket();201accept = new Thread(this::accept);202}203204void start() throws IOException {205ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));206accept.start();207}208209// Pipe the input stream to the output stream210private synchronized Thread pipe(InputStream is, OutputStream os, char tag) {211return new Thread("TunnelPipe(" + tag + ")") {212@Override213public void run() {214try {215try {216int c;217while ((c = is.read()) != -1) {218os.write(c);219os.flush();220// if DEBUG prints a + or a - for each transferred221// character.222if (DEBUG) System.out.print(tag);223}224is.close();225} finally {226os.close();227}228} catch (IOException ex) {229if (DEBUG) ex.printStackTrace(System.out);230}231}232};233}234235public InetSocketAddress getAddress() {236return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort());237}238239// This is a bit shaky. It doesn't handle continuation240// lines, but our client shouldn't send any.241// Read a line from the input stream, swallowing the final242// \r\n sequence. Stops at the first \n, doesn't complain243// if it wasn't preceded by '\r'.244//245String readLine(InputStream r) throws IOException {246StringBuilder b = new StringBuilder();247int c;248while ((c = r.read()) != -1) {249if (c == '\n') {250break;251}252b.appendCodePoint(c);253}254if (b.codePointAt(b.length() - 1) == '\r') {255b.delete(b.length() - 1, b.length());256}257return b.toString();258}259260public void accept() {261Socket clientConnection = null;262try {263while (true) {264System.out.println("Tunnel: Waiting for client");265Socket previous = clientConnection;266try {267clientConnection = ss.accept();268} catch (IOException io) {269if (DEBUG) io.printStackTrace(System.out);270break;271} finally {272// we have only 1 client at a time, so it is safe273// to close the previous connection here274if (previous != null) previous.close();275}276System.out.println("Tunnel: Client accepted");277Socket targetConnection = null;278InputStream ccis = clientConnection.getInputStream();279OutputStream ccos = clientConnection.getOutputStream();280Writer w = new OutputStreamWriter(ccos, "UTF-8");281PrintWriter pw = new PrintWriter(w);282System.out.println("Tunnel: Reading request line");283String requestLine = readLine(ccis);284System.out.println("Tunnel: Request status line: " + requestLine);285if (requestLine.startsWith("CONNECT ")) {286// We should probably check that the next word following287// CONNECT is the host:port of our HTTPS serverImpl.288// Some improvement for a followup!289290// Read all headers until we find the empty line that291// signals the end of all headers.292while (!requestLine.equals("")) {293System.out.println("Tunnel: Reading header: "294+ (requestLine = readLine(ccis)));295}296297// Open target connection298targetConnection = new Socket(299serverImpl.getAddress().getAddress(),300serverImpl.getAddress().getPort());301302// Then send the 200 OK response to the client303System.out.println("Tunnel: Sending "304+ "HTTP/1.1 200 OK\r\n\r\n");305pw.print("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");306pw.flush();307} else {308// This should not happen309throw new IOException("Tunnel: Unexpected status line: "310+ requestLine);311}312313// Pipe the input stream of the client connection to the314// output stream of the target connection and conversely.315// Now the client and target will just talk to each other.316System.out.println("Tunnel: Starting tunnel pipes");317Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+');318Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-');319t1.start();320t2.start();321322// We have only 1 client... wait until it has finished before323// accepting a new connection request.324System.out.println("Tunnel: Waiting for pipes to close");325t1.join();326t2.join();327System.out.println("Tunnel: Done - waiting for next client");328}329} catch (Throwable ex) {330try {331ss.close();332} catch (IOException ex1) {333ex.addSuppressed(ex1);334}335ex.printStackTrace(System.err);336}337}338339void stop() throws IOException {340ss.close();341}342}343344static class Configurator extends HttpsConfigurator {345public Configurator(SSLContext ctx) {346super(ctx);347}348349@Override350public void configure(HttpsParameters params) {351params.setSSLParameters(getSSLContext().getSupportedSSLParameters());352}353}354355356static class TestSSLContext {357358SSLContext ssl;359360public TestSSLContext() throws Exception {361init();362}363364private void init() throws Exception {365366CertAndKeyGen keyGen = new CertAndKeyGen("RSA", "SHA1WithRSA", null);367keyGen.generate(1024);368369//Generate self signed certificate370X509Certificate[] chain = new X509Certificate[1];371chain[0] = keyGen.getSelfCertificate(new X500Name("CN=ROOT"), (long) 365 * 24 * 3600);372373char[] passphrase = "passphrase".toCharArray();374375KeyStore ks = KeyStore.getInstance("JKS");376ks.load(null, passphrase); // must be "initialized" ...377378ks.setKeyEntry("server", keyGen.getPrivateKey(), passphrase, chain);379380KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");381kmf.init(ks, passphrase);382383TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");384tmf.init(ks);385386ssl = SSLContext.getInstance("TLS");387ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);388}389390public SSLContext get() {391return ssl;392}393}394395// ###############################################################################################396397private static String toString(InputStream inputStream) throws IOException {398StringBuilder sb = new StringBuilder();399BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));400int i = bufferedReader.read();401while (i != -1) {402sb.append((char) i);403i = bufferedReader.read();404}405bufferedReader.close();406return sb.toString();407}408}409410411