From d1ea951d39f27d0b5d40a26cd94489074edd5d87 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Thu, 6 Feb 2025 11:49:15 +0000 Subject: [PATCH] 8345625: Better HTTP connections Reviewed-by: skoivu, rhalade, ahgross, dfuchs, jpai, aefimov --- .../classes/sun/net/ftp/impl/FtpClient.java | 8 +-- .../share/classes/sun/net/util/ProxyUtil.java | 49 +++++++++++++++++++ .../classes/sun/net/www/http/HttpClient.java | 10 ++-- .../www/protocol/ftp/FtpURLConnection.java | 4 +- .../sun/net/www/protocol/ftp/Handler.java | 14 +++--- .../www/protocol/http/HttpURLConnection.java | 5 +- .../jdk/internal/net/http/HttpClientImpl.java | 5 -- .../internal/net/http/HttpRequestImpl.java | 45 +++++++++-------- .../jdk/internal/net/http/common/Utils.java | 15 +++++- .../net/http/websocket/OpeningHandshake.java | 3 +- 10 files changed, 109 insertions(+), 49 deletions(-) create mode 100644 src/java.base/share/classes/sun/net/util/ProxyUtil.java diff --git a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java index d5c9a3feb44..1d6876f760e 100644 --- a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java +++ b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, 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 @@ -24,8 +24,6 @@ */ package sun.net.ftp.impl; - - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -61,6 +59,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; + import sun.net.ftp.FtpDirEntry; import sun.net.ftp.FtpDirParser; import sun.net.ftp.FtpProtocolException; @@ -68,6 +67,7 @@ import sun.net.ftp.FtpReplyCode; import sun.net.util.IPAddressUtil; import sun.util.logging.PlatformLogger; +import static sun.net.util.ProxyUtil.copyProxy; public class FtpClient extends sun.net.ftp.FtpClient { @@ -954,7 +954,7 @@ public class FtpClient extends sun.net.ftp.FtpClient { } public sun.net.ftp.FtpClient setProxy(Proxy p) { - proxy = p; + proxy = copyProxy(p); return this; } diff --git a/src/java.base/share/classes/sun/net/util/ProxyUtil.java b/src/java.base/share/classes/sun/net/util/ProxyUtil.java new file mode 100644 index 00000000000..dfb60671e37 --- /dev/null +++ b/src/java.base/share/classes/sun/net/util/ProxyUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.net.util; + +import sun.net.ApplicationProxy; + +import java.net.Proxy; + +public final class ProxyUtil { + + private ProxyUtil() {} + + /** + * Creates a new {@link Proxy} instance for the given proxy iff it is + * neither null, {@link Proxy#NO_PROXY Proxy.NO_PROXY}, an + * {@link ApplicationProxy} instance, nor already a {@code Proxy} instance. + */ + public static Proxy copyProxy(Proxy proxy) { + return proxy == null + || proxy.getClass() == Proxy.class + || proxy instanceof ApplicationProxy + ? proxy + : new Proxy(proxy.type(), proxy.address()); + } + +} diff --git a/src/java.base/share/classes/sun/net/www/http/HttpClient.java b/src/java.base/share/classes/sun/net/www/http/HttpClient.java index 1ab7396ad06..82ab4c199a5 100644 --- a/src/java.base/share/classes/sun/net/www/http/HttpClient.java +++ b/src/java.base/share/classes/sun/net/www/http/HttpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2025, 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 @@ -41,6 +41,8 @@ import sun.net.www.ParseUtil; import sun.net.www.protocol.http.AuthCacheImpl; import sun.net.www.protocol.http.HttpURLConnection; import sun.util.logging.PlatformLogger; + +import static sun.net.util.ProxyUtil.copyProxy; import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; /** @@ -261,7 +263,7 @@ public class HttpClient extends NetworkClient { } protected HttpClient(URL url, Proxy p, int to) throws IOException { - proxy = (p == null) ? Proxy.NO_PROXY : p; + proxy = p == null ? Proxy.NO_PROXY : copyProxy(p); this.host = url.getHost(); this.url = url; port = url.getPort(); @@ -326,9 +328,7 @@ public class HttpClient extends NetworkClient { public static HttpClient New(URL url, Proxy p, int to, boolean useCache, HttpURLConnection httpuc) throws IOException { - if (p == null) { - p = Proxy.NO_PROXY; - } + p = p == null ? Proxy.NO_PROXY : copyProxy(p); HttpClient ret = null; /* see if one's already around */ if (useCache) { diff --git a/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java index be24f1133f2..eb7a819bd47 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java @@ -43,6 +43,7 @@ import java.net.ProxySelector; import java.util.List; import java.util.StringTokenizer; import java.security.Permission; + import sun.net.NetworkClient; import sun.net.util.IPAddressUtil; import sun.net.www.MessageHeader; @@ -53,6 +54,7 @@ import sun.net.ftp.FtpClient; import sun.net.ftp.FtpProtocolException; import sun.net.www.ParseUtil; +import static sun.net.util.ProxyUtil.copyProxy; /** * This class Opens an FTP input (or output) stream given a URL. @@ -234,7 +236,7 @@ public class FtpURLConnection extends URLConnection { throw new IOException("Failed to select a proxy", iae); } for (Proxy proxy : proxies) { - p = proxy; + p = copyProxy(proxy); if (p == null || p == Proxy.NO_PROXY || p.type() == Proxy.Type.SOCKS) { break; diff --git a/src/java.base/share/classes/sun/net/www/protocol/ftp/Handler.java b/src/java.base/share/classes/sun/net/www/protocol/ftp/Handler.java index 1de4c52abea..53fa355cb01 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/ftp/Handler.java +++ b/src/java.base/share/classes/sun/net/www/protocol/ftp/Handler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2025, 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 @@ -33,11 +33,9 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.Proxy; -import java.util.Map; -import java.util.HashMap; import java.util.Objects; -import sun.net.ftp.FtpClient; -import sun.net.www.protocol.http.HttpURLConnection; + +import static sun.net.util.ProxyUtil.copyProxy; /** open an ftp connection given a URL */ public class Handler extends java.net.URLStreamHandler { @@ -57,11 +55,11 @@ public class Handler extends java.net.URLStreamHandler { return openConnection(u, null); } - protected java.net.URLConnection openConnection(URL u, Proxy p) + protected java.net.URLConnection openConnection(URL u, Proxy proxy) throws IOException { - FtpURLConnection connection = null; + FtpURLConnection connection; try { - connection = new FtpURLConnection(u, p); + connection = new FtpURLConnection(u, copyProxy(proxy)); } catch (IllegalArgumentException e) { var mfue = new MalformedURLException(e.getMessage()); mfue.initCause(e); diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index b66d9ddb8cb..857c2f6ad6d 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -77,6 +77,7 @@ import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; +import static sun.net.util.ProxyUtil.copyProxy; import static sun.net.www.protocol.http.AuthScheme.BASIC; import static sun.net.www.protocol.http.AuthScheme.DIGEST; import static sun.net.www.protocol.http.AuthScheme.NTLM; @@ -854,7 +855,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection { responses = new MessageHeader(maxHeaderSize); userHeaders = new MessageHeader(); this.handler = handler; - instProxy = p; + instProxy = copyProxy(p); cookieHandler = CookieHandler.getDefault(); cacheHandler = ResponseCache.getDefault(); } @@ -956,7 +957,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection { final Iterator it = proxies.iterator(); Proxy p; while (it.hasNext()) { - p = it.next(); + p = copyProxy(it.next()); try { if (!failedOnce) { http = getNewHttpClient(url, p, connectTimeout); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java index c58f0b0c752..bfdd43797b0 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java @@ -1636,11 +1636,6 @@ final class HttpClientImpl extends HttpClient implements Trackable { return Optional.ofNullable(userProxySelector); } - // Return the effective proxy that this client uses. - ProxySelector proxySelector() { - return proxySelector; - } - @Override public WebSocket.Builder newWebSocketBuilder() { // Make sure to pass the HttpClientFacade to the WebSocket builder. diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java index bb794031508..4d53a85ab78 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -45,10 +45,10 @@ import jdk.internal.net.http.common.HttpHeadersBuilder; import jdk.internal.net.http.common.Utils; import jdk.internal.net.http.websocket.WebSocketRequest; -import static java.net.Authenticator.RequestorType.PROXY; import static java.net.Authenticator.RequestorType.SERVER; import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS; import static jdk.internal.net.http.common.Utils.ProxyHeaders; +import static jdk.internal.net.http.common.Utils.copyProxy; public class HttpRequestImpl extends HttpRequest implements WebSocketRequest { @@ -127,15 +127,7 @@ public class HttpRequestImpl extends HttpRequest implements WebSocketRequest { this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT); } this.uri = requestURI; - if (isWebSocket) { - // WebSocket determines and sets the proxy itself - this.proxy = ((HttpRequestImpl) request).proxy; - } else { - if (ps != null) - this.proxy = retrieveProxy(ps, uri); - else - this.proxy = null; - } + this.proxy = retrieveProxy(request, ps, uri); this.expectContinue = request.expectContinue(); this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https"); this.requestPublisher = request.bodyPublisher().orElse(null); @@ -292,16 +284,27 @@ public class HttpRequestImpl extends HttpRequest implements WebSocketRequest { @Override public boolean expectContinue() { return expectContinue; } - /** Retrieves the proxy, from the given ProxySelector, if there is one. */ - private static Proxy retrieveProxy(ProxySelector ps, URI uri) { - Proxy proxy = null; - List pl = ps.select(uri); - if (!pl.isEmpty()) { - Proxy p = pl.get(0); - if (p.type() == Proxy.Type.HTTP) - proxy = p; + /** Retrieves a copy of the proxy either from the given {@link HttpRequest} or {@link ProxySelector}, if there is one. */ + private static Proxy retrieveProxy(HttpRequest request, ProxySelector ps, URI uri) { + + // WebSocket determines and sets the proxy itself + if (request instanceof HttpRequestImpl requestImpl && requestImpl.isWebSocket) { + return requestImpl.proxy; } - return proxy; + + // Try to find a matching one from the `ProxySelector` + if (ps != null) { + List pl = ps.select(uri); + if (!pl.isEmpty()) { + Proxy p = pl.getFirst(); + if (p.type() == Proxy.Type.HTTP) { + return copyProxy(p); + } + } + } + + return null; + } InetSocketAddress proxy() { @@ -317,7 +320,7 @@ public class HttpRequestImpl extends HttpRequest implements WebSocketRequest { @Override public void setProxy(Proxy proxy) { assert isWebSocket; - this.proxy = proxy; + this.proxy = copyProxy(proxy); } @Override diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java index d035a8c8da1..eecabfd96f9 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -38,6 +38,7 @@ import java.io.UncheckedIOException; import java.lang.System.Logger.Level; import java.net.ConnectException; import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URI; import java.net.http.HttpHeaders; import java.net.http.HttpTimeoutException; @@ -78,7 +79,6 @@ import sun.net.www.HeaderParser; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.US_ASCII; -import static java.util.stream.Collectors.joining; import static java.net.Authenticator.RequestorType.PROXY; import static java.net.Authenticator.RequestorType.SERVER; @@ -309,6 +309,17 @@ public final class Utils { : ! PROXY_AUTH_DISABLED_SCHEMES.isEmpty(); } + /** + * Creates a new {@link Proxy} instance for the given proxy iff it is + * neither null, {@link Proxy#NO_PROXY Proxy.NO_PROXY}, nor already a + * {@code Proxy} instance. + */ + public static Proxy copyProxy(Proxy proxy) { + return proxy == null || proxy.getClass() == Proxy.class + ? proxy + : new Proxy(proxy.type(), proxy.address()); + } + // WebSocket connection Upgrade headers private static final String HEADER_CONNECTION = "Connection"; private static final String HEADER_UPGRADE = "Upgrade"; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java index 6e76a272cd0..09bfecefcab 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java @@ -59,6 +59,7 @@ import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import static java.lang.String.format; +import static jdk.internal.net.http.common.Utils.copyProxy; import static jdk.internal.net.http.common.Utils.isValidName; import static jdk.internal.net.http.common.Utils.stringOf; import static jdk.internal.util.Exceptions.filterNonSocketInfo; @@ -369,7 +370,7 @@ public class OpeningHandshake { if (proxy.type() != Proxy.Type.HTTP) { return null; } - return proxy; + return copyProxy(proxy); } }