--- old/src/java.base/share/classes/java/net/HttpURLConnection.java 2019-10-29 12:39:51.000000000 +0000 +++ new/src/java.base/share/classes/java/net/HttpURLConnection.java 2019-10-29 12:39:51.000000000 +0000 @@ -618,8 +618,13 @@ /** * Indicates if the connection is going through a proxy. - * @return a boolean indicating if the connection is - * using a proxy. + * + * This method returns {@code true} if the connection is known + * to be going or has gone through proxies, and returns {@code false} + * if the connection will never go through a proxy or if + * the use of a proxy cannot be determined. + * + * @return a boolean indicating if the connection is using a proxy. */ public abstract boolean usingProxy(); --- old/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java 2019-10-29 12:39:52.000000000 +0000 +++ new/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java 2019-10-29 12:39:52.000000000 +0000 @@ -312,6 +312,8 @@ private CookieHandler cookieHandler; private final ResponseCache cacheHandler; + private volatile boolean usingProxy; + // the cached response, and cached response headers and body protected CacheResponse cachedResponse; private MessageHeader cachedHeaders; @@ -320,7 +322,6 @@ /* output stream to server */ protected PrintStream ps = null; - /* buffered error stream */ private InputStream errorStream = null; @@ -1240,6 +1241,7 @@ } } + usingProxy = usingProxy || usingProxyInternal(); ps = (PrintStream)http.getOutputStream(); } catch (IOException e) { throw e; @@ -2917,7 +2919,7 @@ * closed the connection to the web server. */ private void disconnectWeb() throws IOException { - if (usingProxy() && http.isKeepingAlive()) { + if (usingProxyInternal() && http.isKeepingAlive()) { responseCode = -1; // clean up, particularly, skip the content part // of a 401 error response @@ -3020,13 +3022,31 @@ } } - public boolean usingProxy() { + /** + * Returns true only if the established connection is using a proxy + */ + boolean usingProxyInternal() { if (http != null) { return (http.getProxyHostUsed() != null); } return false; } + /** + * Returns true if the established connection is using a proxy + * or if a proxy is specified for the inactive connection + */ + @Override + public boolean usingProxy() { + if (usingProxy || usingProxyInternal()) + return true; + + if (instProxy != null) + return instProxy.type().equals(Proxy.Type.HTTP); + + return false; + } + // constant strings represent set-cookie header names private static final String SET_COOKIE = "set-cookie"; private static final String SET_COOKIE2 = "set-cookie2"; --- /dev/null 2019-10-29 12:39:53.000000000 +0000 +++ new/test/jdk/java/net/HttpURLConnection/HttpURLConnUsingProxy.java 2019-10-29 12:39:52.000000000 +0000 @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2019, 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 + * @bug 8231632 + * @summary HttpURLConnection::usingProxy could specify that it lazily evaluates the fact + * @modules java.base/sun.net.www + * @library /test/lib + * @run main/othervm HttpURLConnUsingProxy + */ + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import jdk.test.lib.net.URIBuilder; + +public class HttpURLConnUsingProxy { + static HttpServer server; + static Proxy proxy; + static InetSocketAddress isa; + + static class Handler implements HttpHandler { + + @Override + public void handle(HttpExchange exchange) throws IOException { + byte[] response = "Hello World!".getBytes(StandardCharsets.UTF_8); + try (InputStream req = exchange.getRequestBody()) { + req.readAllBytes(); + } + exchange.sendResponseHeaders(200, response.length); + try (OutputStream resp = exchange.getResponseBody()) { + resp.write(response); + } + } + } + + public static void main(String[] args) { + try { + InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); + InetSocketAddress addr = new InetSocketAddress(loopbackAddress, 0); + server = HttpServer.create(addr, 0); + server.createContext("/HttpURLConnUsingProxy/http1/", new Handler()); + server.start(); + + ProxyServer pserver = new ProxyServer(loopbackAddress, + server.getAddress().getPort()); + // Start proxy server + new Thread(pserver).start(); + + URL url = URIBuilder.newBuilder() + .scheme("http") + .loopback() + .port(server.getAddress().getPort()) + .path("/HttpURLConnUsingProxy/http1/x.html") + .toURLUnchecked(); + + // NO_PROXY + try { + HttpURLConnection urlc = + (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); + assertEqual(urlc.usingProxy(), false); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), false); + urlc.disconnect(); + } catch (IOException ioe) { + throw new RuntimeException("Direct connection should succeed: " + + ioe.getMessage()); + } + + // Non-existing proxy + try { + isa = InetSocketAddress.createUnresolved("inexistent", 8080); + proxy = new Proxy(Proxy.Type.HTTP, isa); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(proxy); + assertEqual(urlc.usingProxy(), true); + InputStream is = urlc.getInputStream(); + is.close(); + throw new RuntimeException("Non-existing proxy should cause IOException"); + } catch (IOException ioe) { + // expected + } + + // Normal proxy settings + try { + isa = InetSocketAddress.createUnresolved(loopbackAddress.getHostAddress(), + pserver.getPort()); + proxy = new Proxy(Proxy.Type.HTTP, isa); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(proxy); + assertEqual(urlc.usingProxy(), true); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), true); + urlc.disconnect(); + } catch (IOException ioe) { + throw new RuntimeException("Connection through local proxy should succeed: " + + ioe.getMessage()); + } + + // Reuse proxy with new HttpURLConnection + try { + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(proxy); + assertEqual(urlc.usingProxy(), true); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), true); + read(urlc.getInputStream()); + assertEqual(urlc.usingProxy(), true); + } catch (IOException ioe) { + throw new RuntimeException("Connection through local proxy should succeed: " + + ioe.getMessage()); + } + + // Reuse proxy with existing HttpURLConnection + try { + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(proxy); + assertEqual(urlc.usingProxy(), true); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), true); + read(urlc.getInputStream()); + assertEqual(urlc.usingProxy(), true); + urlc.disconnect(); + } catch (IOException ioe) { + throw new RuntimeException("Connection through local proxy should succeed: " + + ioe.getMessage()); + } + + // ProxySelector with normal proxy settings + try { + ProxySelector.setDefault(ProxySelector.of(isa)); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(); + assertEqual(urlc.usingProxy(), false); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), true); + read(urlc.getInputStream()); + assertEqual(urlc.usingProxy(), true); + urlc.disconnect(); + assertEqual(urlc.usingProxy(), true); + } catch (IOException ioe) { + throw new RuntimeException("Connection through local proxy should succeed: " + + ioe.getMessage()); + } + + // ProxySelector with proxying disabled + try { + ProxySelector.setDefault(ProxySelector.of(null)); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(); + assertEqual(urlc.usingProxy(), false); + urlc.getResponseCode(); + assertEqual(urlc.usingProxy(), false); + read(urlc.getInputStream()); + assertEqual(urlc.usingProxy(), false); + } catch (IOException ioe) { + throw new RuntimeException("Direct connection should succeed: " + + ioe.getMessage()); + } + + // ProxySelector overwritten + try { + ProxySelector.setDefault(ProxySelector.of(isa)); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); + assertEqual(urlc.usingProxy(), false); + urlc.disconnect(); + } catch (IOException ioe) { + throw new RuntimeException("Direct connection should succeed: " + + ioe.getMessage()); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (server != null) { + server.stop(0); + } + } + } + + static class ProxyServer extends Thread { + private static ServerSocket ss = null; + + // Client requesting a tunnel + private Socket clientSocket = null; + + /* + * Origin server's address and port that the client + * wants to establish the tunnel for communication. + */ + private InetAddress serverInetAddr; + private int serverPort; + + public ProxyServer(InetAddress server, int port) throws IOException { + serverInetAddr = server; + serverPort = port; + ss = new ServerSocket(0, 0, InetAddress.getLoopbackAddress()); + } + + public void run() { + while (true) { + try { + clientSocket = ss.accept(); + processRequests(); + } catch (Exception e) { + System.out.println("Proxy failed: " + e); + e.printStackTrace(); + try { + ss.close(); + } catch (IOException ioe) { + System.out.println("ProxyServer close error: " + ioe); + ioe.printStackTrace(); + } + } + } + } + + private void processRequests() throws Exception { + // Connection set to tunneling mode + + Socket serverSocket = new Socket(serverInetAddr, serverPort); + ProxyTunnel clientToServer = new ProxyTunnel( + clientSocket, serverSocket); + ProxyTunnel serverToClient = new ProxyTunnel( + serverSocket, clientSocket); + clientToServer.start(); + serverToClient.start(); + System.out.println("Proxy: Started tunneling..."); + + clientToServer.join(); + serverToClient.join(); + System.out.println("Proxy: Finished tunneling..."); + + clientToServer.close(); + serverToClient.close(); + } + + /** + * ************************************************************** + * Helper methods follow + * ************************************************************** + */ + public int getPort() { + return ss.getLocalPort(); + } + + /* + * This inner class provides unidirectional data flow through the sockets + * by continuously copying bytes from input socket to output socket + * while both sockets are open and EOF has not been received. + */ + static class ProxyTunnel extends Thread { + Socket sockIn; + Socket sockOut; + InputStream input; + OutputStream output; + + public ProxyTunnel(Socket sockIn, Socket sockOut) throws Exception { + this.sockIn = sockIn; + this.sockOut = sockOut; + input = sockIn.getInputStream(); + output = sockOut.getOutputStream(); + } + + public void run() { + int BUFFER_SIZE = 400; + byte[] buf = new byte[BUFFER_SIZE]; + int bytesRead = 0; + int count = 0; // Keep track of amount of data transferred + + try { + while ((bytesRead = input.read(buf)) >= 0) { + output.write(buf, 0, bytesRead); + output.flush(); + count += bytesRead; + } + } catch (IOException e) { + /* + * Peer end has closed connection + * so we close tunnel + */ + close(); + } + } + + public void close() { + try { + if (!sockIn.isClosed()) + sockIn.close(); + if (!sockOut.isClosed()) + sockOut.close(); + } catch (IOException ignored) { + } + } + } + } + + private static void assertEqual(boolean usingProxy, boolean expected) { + if (usingProxy != expected) { + throw new RuntimeException("Expected: " + expected + + " but usingProxy returned: " + usingProxy); + } + } + + private static String read(InputStream inputStream) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + int i = bufferedReader.read(); + while (i != -1) { + sb.append((char) i); + i = bufferedReader.read(); + } + bufferedReader.close(); + return sb.toString(); + } +}