8209137: Add ability to bind to specific local address to HTTP client
Reviewed-by: dfuchs, michaelm
This commit is contained in:
parent
65da38d844
commit
f4258a50e0
@ -21,6 +21,8 @@ grant codeBase "jrt:/java.net.http" {
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
|
||||
permission java.lang.RuntimePermission "modifyThread";
|
||||
permission java.net.SocketPermission "*","connect,resolve";
|
||||
// required if the HTTPClient is configured to use a local bind address
|
||||
permission java.net.SocketPermission "localhost:*","listen,resolve";
|
||||
permission java.net.URLPermission "http:*","*:*";
|
||||
permission java.net.URLPermission "https:*","*:*";
|
||||
permission java.net.URLPermission "ws:*","*:*";
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2022, 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
|
||||
@ -27,6 +27,7 @@ package java.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.channels.Selector;
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
@ -354,16 +355,59 @@ public abstract class HttpClient {
|
||||
*/
|
||||
public Builder authenticator(Authenticator authenticator);
|
||||
|
||||
/**
|
||||
* Binds the socket to this local address when creating
|
||||
* connections for sending requests.
|
||||
*
|
||||
* <p> If no local address is set or {@code null} is passed
|
||||
* to this method then sockets created by the
|
||||
* HTTP client will be bound to an automatically
|
||||
* assigned socket address.
|
||||
*
|
||||
* <p> Common usages of the {@code HttpClient} do not require
|
||||
* this method to be called. Setting a local address, through this
|
||||
* method, is only for advanced usages where users of the {@code HttpClient}
|
||||
* require specific control on which network interface gets used
|
||||
* for the HTTP communication. Callers of this method are expected to
|
||||
* be aware of the networking configurations of the system where the
|
||||
* {@code HttpClient} will be used and care should be taken to ensure the
|
||||
* correct {@code localAddr} is passed. Failure to do so can result in
|
||||
* requests sent through the {@code HttpClient} to fail.
|
||||
*
|
||||
* @implSpec The default implementation of this method throws
|
||||
* {@code UnsupportedOperationException}. {@code Builder}s obtained
|
||||
* through {@link HttpClient#newBuilder()} provide an implementation
|
||||
* of this method that allows setting the local address.
|
||||
*
|
||||
* @param localAddr The local address of the socket. Can be null.
|
||||
* @return this builder
|
||||
* @throws UnsupportedOperationException if this builder doesn't support
|
||||
* configuring a local address or if the passed {@code localAddr}
|
||||
* is not supported by this {@code HttpClient} implementation.
|
||||
* @since 19
|
||||
*/
|
||||
default Builder localAddress(InetAddress localAddr) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link HttpClient} built from the current state of this
|
||||
* builder.
|
||||
*
|
||||
* @implSpec If the {@link #localAddress(InetAddress) local address} is a non-null
|
||||
* address and a security manager is installed, then
|
||||
* this method calls {@link SecurityManager#checkListen checkListen} to check that
|
||||
* the caller has necessary permission to bind to that local address.
|
||||
*
|
||||
* @return a new {@code HttpClient}
|
||||
*
|
||||
* @throws UncheckedIOException may be thrown if underlying IO resources required
|
||||
* by the implementation cannot be allocated. For instance,
|
||||
* if the implementation requires a {@link Selector}, and opening
|
||||
* one fails due to {@linkplain Selector#open() lack of necessary resources}.
|
||||
* @throws SecurityException If a security manager has been installed and the
|
||||
* security manager's {@link SecurityManager#checkListen checkListen}
|
||||
* method disallows binding to the given address.
|
||||
*/
|
||||
public HttpClient build();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2022, 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
|
||||
@ -27,6 +27,7 @@ package jdk.internal.net.http;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executor;
|
||||
@ -49,6 +50,7 @@ public class HttpClientBuilderImpl implements HttpClient.Builder {
|
||||
SSLContext sslContext;
|
||||
SSLParameters sslParams;
|
||||
int priority = -1;
|
||||
InetAddress localAddr;
|
||||
|
||||
@Override
|
||||
public HttpClientBuilderImpl cookieHandler(CookieHandler cookieHandler) {
|
||||
@ -130,6 +132,12 @@ public class HttpClientBuilderImpl implements HttpClient.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder localAddress(final InetAddress localAddr) {
|
||||
this.localAddr = localAddr;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient build() {
|
||||
return HttpClientImpl.create(this);
|
||||
|
@ -36,6 +36,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.net.Authenticator;
|
||||
import java.net.ConnectException;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.http.HttpConnectTimeoutException;
|
||||
import java.net.http.HttpTimeoutException;
|
||||
@ -335,6 +336,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
private final Http2ClientImpl client2;
|
||||
private final long id;
|
||||
private final String dbgTag;
|
||||
private final InetAddress localAddr;
|
||||
|
||||
// The SSL DirectBuffer Supplier provides the ability to recycle
|
||||
// buffers used between the socket reader and the SSLEngine, or
|
||||
@ -431,6 +433,17 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
SingleFacadeFactory facadeFactory) {
|
||||
id = CLIENT_IDS.incrementAndGet();
|
||||
dbgTag = "HttpClientImpl(" + id +")";
|
||||
@SuppressWarnings("removal")
|
||||
var sm = System.getSecurityManager();
|
||||
if (sm != null && builder.localAddr != null) {
|
||||
// when a specific local address is configured, it will eventually
|
||||
// lead to the SocketChannel.bind(...) call with an InetSocketAddress
|
||||
// whose InetAddress is the local address and the port is 0. That ultimately
|
||||
// leads to a SecurityManager.checkListen permission check for that port.
|
||||
// so we do that security manager check here with port 0.
|
||||
sm.checkListen(0);
|
||||
}
|
||||
localAddr = builder.localAddr;
|
||||
if (builder.sslContext == null) {
|
||||
try {
|
||||
sslContext = SSLContext.getDefault();
|
||||
@ -1528,6 +1541,10 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
return version;
|
||||
}
|
||||
|
||||
InetAddress localAddress() {
|
||||
return localAddr;
|
||||
}
|
||||
|
||||
String dbgString() {
|
||||
return dbgTag;
|
||||
}
|
||||
|
@ -183,6 +183,27 @@ class PlainHttpConnection extends HttpConnection {
|
||||
}
|
||||
}
|
||||
|
||||
var localAddr = client().localAddress();
|
||||
if (localAddr != null) {
|
||||
if (debug.on()) {
|
||||
debug.log("binding to configured local address " + localAddr);
|
||||
}
|
||||
var sockAddr = new InetSocketAddress(localAddr, 0);
|
||||
PrivilegedExceptionAction<SocketChannel> pa = () -> chan.bind(sockAddr);
|
||||
try {
|
||||
AccessController.doPrivileged(pa);
|
||||
if (debug.on()) {
|
||||
debug.log("bind completed " + localAddr);
|
||||
}
|
||||
} catch (PrivilegedActionException e) {
|
||||
var cause = e.getCause();
|
||||
if (debug.on()) {
|
||||
debug.log("bind to " + localAddr + " failed: " + cause.getMessage());
|
||||
}
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
PrivilegedExceptionAction<Boolean> pa =
|
||||
() -> chan.connect(Utils.resolveAddress(address));
|
||||
try {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2022, 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
|
||||
@ -26,12 +26,12 @@ import java.lang.reflect.Method;
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandler;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
@ -40,7 +40,6 @@ import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@ -55,6 +54,7 @@ import static org.testng.Assert.*;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8209137
|
||||
* @summary HttpClient[.Builder] API and behaviour checks
|
||||
* @library /test/lib
|
||||
* @build jdk.test.lib.net.SimpleSSLContext
|
||||
@ -65,6 +65,7 @@ public class HttpClientBuilderTest {
|
||||
|
||||
static final Class<NullPointerException> NPE = NullPointerException.class;
|
||||
static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
|
||||
static final Class<UnsupportedOperationException> UOE = UnsupportedOperationException.class;
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws Exception {
|
||||
@ -262,6 +263,89 @@ public class HttpClientBuilderTest {
|
||||
builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the {@link java.net.http.HttpClient.Builder#localAddress(InetAddress)} method
|
||||
* behaviour when that method is called on a builder returned by {@link HttpClient#newBuilder()}
|
||||
*/
|
||||
@Test
|
||||
public void testLocalAddress() throws Exception {
|
||||
HttpClient.Builder builder = HttpClient.newBuilder();
|
||||
// setting null should work fine
|
||||
builder.localAddress(null);
|
||||
builder.localAddress(InetAddress.getLoopbackAddress());
|
||||
// resetting back to null should work fine
|
||||
builder.localAddress(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the default method implementation of
|
||||
* {@link java.net.http.HttpClient.Builder#localAddress(InetAddress)} throws
|
||||
* an {@link UnsupportedOperationException}
|
||||
*/
|
||||
@Test
|
||||
public void testDefaultMethodImplForLocalAddress() throws Exception {
|
||||
HttpClient.Builder noOpBuilder = new HttpClient.Builder() {
|
||||
@Override
|
||||
public HttpClient.Builder cookieHandler(CookieHandler cookieHandler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder connectTimeout(Duration duration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder sslContext(SSLContext sslContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder sslParameters(SSLParameters sslParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder executor(Executor executor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder followRedirects(Redirect policy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder version(Version version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder priority(int priority) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder proxy(ProxySelector proxySelector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient.Builder authenticator(Authenticator authenticator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient build() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// expected to throw a UnsupportedOperationException
|
||||
assertThrows(UOE, () -> noOpBuilder.localAddress(null));
|
||||
// a non-null address should also throw a UnsupportedOperationException
|
||||
assertThrows(UOE, () -> noOpBuilder.localAddress(InetAddress.getLoopbackAddress()));
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
static final URI uri = URI.create("http://foo.com/");
|
||||
@ -303,9 +387,6 @@ public class HttpClientBuilderTest {
|
||||
|
||||
// ---
|
||||
|
||||
static final Class<UnsupportedOperationException> UOE =
|
||||
UnsupportedOperationException.class;
|
||||
|
||||
@Test
|
||||
static void testUnsupportedWebSocket() throws Exception {
|
||||
// @implSpec The default implementation of this method throws
|
||||
|
329
test/jdk/java/net/httpclient/HttpClientLocalAddrTest.java
Normal file
329
test/jdk/java/net/httpclient/HttpClientLocalAddrTest.java
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.sun.net.httpserver.HttpsConfigurator;
|
||||
import com.sun.net.httpserver.HttpsServer;
|
||||
import jdk.test.lib.net.IPSupport;
|
||||
import jdk.test.lib.net.SimpleSSLContext;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
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.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests HttpClient usage when configured with a local address to bind
|
||||
* to, when sending requests
|
||||
* @bug 8209137
|
||||
* @modules jdk.httpserver
|
||||
* java.net.http/jdk.internal.net.http.common
|
||||
* java.net.http/jdk.internal.net.http.frame
|
||||
* java.base/sun.net.www.http
|
||||
* java.net.http/jdk.internal.net.http.hpack
|
||||
*
|
||||
* @library /test/lib http2/server
|
||||
*
|
||||
* @build jdk.test.lib.net.SimpleSSLContext jdk.test.lib.net.IPSupport HttpServerAdapters
|
||||
*
|
||||
* @run testng/othervm
|
||||
* -Djdk.httpclient.HttpClient.log=frames,ssl,requests,responses,errors
|
||||
* -Djdk.internal.httpclient.debug=true
|
||||
* HttpClientLocalAddrTest
|
||||
*
|
||||
* @run testng/othervm/java.security.policy=httpclient-localaddr-security.policy
|
||||
* -Djdk.httpclient.HttpClient.log=frames,ssl,requests,responses,errors
|
||||
* -Djdk.internal.httpclient.debug=true
|
||||
* HttpClientLocalAddrTest
|
||||
*
|
||||
*/
|
||||
public class HttpClientLocalAddrTest implements HttpServerAdapters {
|
||||
|
||||
private static SSLContext sslContext;
|
||||
private static HttpServerAdapters.HttpTestServer http1_1_Server;
|
||||
private static URI httpURI;
|
||||
private static HttpServerAdapters.HttpTestServer https_1_1_Server;
|
||||
private static URI httpsURI;
|
||||
private static HttpServerAdapters.HttpTestServer http2_Server;
|
||||
private static URI http2URI;
|
||||
private static HttpServerAdapters.HttpTestServer https2_Server;
|
||||
private static URI https2URI;
|
||||
|
||||
// start various HTTP/HTTPS servers that will be invoked against in the tests
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
sslContext = new SimpleSSLContext().get();
|
||||
Assert.assertNotNull(sslContext, "Unexpected null sslContext");
|
||||
|
||||
HttpServerAdapters.HttpTestHandler handler = (exchange) -> {
|
||||
// the handler receives a request and sends back a 200 response with the
|
||||
// response body containing the raw IP address (in byte[] form) of the client from whom
|
||||
// the request was received
|
||||
var clientAddr = exchange.getRemoteAddress();
|
||||
System.out.println("Received a request from client address " + clientAddr);
|
||||
var responseContent = clientAddr.getAddress().getAddress();
|
||||
exchange.sendResponseHeaders(200, responseContent.length);
|
||||
try (var os = exchange.getResponseBody()) {
|
||||
// write out the client address as a response
|
||||
os.write(responseContent);
|
||||
}
|
||||
exchange.close();
|
||||
};
|
||||
|
||||
// HTTP/1.1 - create servers with http and https
|
||||
final var sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
||||
final int backlog = 0;
|
||||
http1_1_Server = HttpServerAdapters.HttpTestServer.of(HttpServer.create(sa, backlog));
|
||||
http1_1_Server.addHandler(handler, "/");
|
||||
http1_1_Server.start();
|
||||
System.out.println("Started HTTP v1.1 server at " + http1_1_Server.serverAuthority());
|
||||
httpURI = new URI("http://" + http1_1_Server.serverAuthority() + "/");
|
||||
|
||||
final HttpsServer httpsServer = HttpsServer.create(sa, 0);
|
||||
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
|
||||
https_1_1_Server = HttpServerAdapters.HttpTestServer.of(httpsServer);
|
||||
https_1_1_Server.addHandler(handler, "/");
|
||||
https_1_1_Server.start();
|
||||
System.out.println("Started HTTPS v1.1 server at " + https_1_1_Server.serverAuthority());
|
||||
httpsURI = new URI("https://" + https_1_1_Server.serverAuthority() + "/");
|
||||
|
||||
// HTTP/2 - create servers with http and https
|
||||
http2_Server = HttpServerAdapters.HttpTestServer.of(new Http2TestServer(sa.getHostString(), false, null));
|
||||
http2_Server.addHandler(handler, "/");
|
||||
http2_Server.start();
|
||||
System.out.println("Started HTTP v2 server at " + http2_Server.serverAuthority());
|
||||
http2URI = new URI("http://" + http2_Server.serverAuthority() + "/");
|
||||
|
||||
https2_Server = HttpServerAdapters.HttpTestServer.of(new Http2TestServer(sa.getHostString(), true, sslContext));
|
||||
https2_Server.addHandler(handler, "/");
|
||||
https2_Server.start();
|
||||
System.out.println("Started HTTPS v2 server at " + https2_Server.serverAuthority());
|
||||
https2URI = new URI("https://" + https2_Server.serverAuthority() + "/");
|
||||
}
|
||||
|
||||
// stop each of the started servers
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
// stop each of the server and accumulate any exception
|
||||
// that might happen during stop and finally throw
|
||||
// the accumulated exception(s)
|
||||
var e = safeStop(http1_1_Server, null);
|
||||
e = safeStop(https_1_1_Server, e);
|
||||
e = safeStop(http2_Server, e);
|
||||
e = safeStop(https2_Server, e);
|
||||
// throw any exception that happened during stop
|
||||
if (e != null) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the server and returns (instead of throwing) any exception that might
|
||||
* have occurred during stop. If {@code prevException} is not null then any
|
||||
* exception during stop of the {@code server} will be added as a suppressed
|
||||
* exception to the {@code prevException} and the {@code prevException} will be
|
||||
* returned.
|
||||
*/
|
||||
private static Exception safeStop(HttpServerAdapters.HttpTestServer server, Exception prevException) {
|
||||
if (server == null) {
|
||||
return null;
|
||||
}
|
||||
var serverAuthority = server.serverAuthority();
|
||||
try {
|
||||
server.stop();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to stop server " + serverAuthority);
|
||||
if (prevException == null) {
|
||||
return e;
|
||||
}
|
||||
prevException.addSuppressed(e);
|
||||
return prevException;
|
||||
}
|
||||
return prevException;
|
||||
}
|
||||
|
||||
@DataProvider(name = "params")
|
||||
private Object[][] paramsProvider() throws Exception {
|
||||
final List<Object[]> testMethodParams = new ArrayList();
|
||||
final URI[] requestURIs = new URI[]{httpURI, httpsURI, http2URI, https2URI};
|
||||
final Predicate<URI> requiresSSLContext = (uri) -> uri.getScheme().equals("https");
|
||||
for (var requestURI : requestURIs) {
|
||||
final var configureClientSSL = requiresSSLContext.test(requestURI);
|
||||
// no localAddr set
|
||||
testMethodParams.add(new Object[]{
|
||||
newBuilder(configureClientSSL).build(),
|
||||
requestURI,
|
||||
null
|
||||
});
|
||||
// null localAddr set
|
||||
testMethodParams.add(new Object[]{
|
||||
newBuilder(configureClientSSL).localAddress(null).build(),
|
||||
requestURI,
|
||||
null
|
||||
});
|
||||
// localAddr set to loopback address
|
||||
final var loopbackAddr = InetAddress.getLoopbackAddress();
|
||||
testMethodParams.add(new Object[]{
|
||||
newBuilder(configureClientSSL)
|
||||
.localAddress(loopbackAddr)
|
||||
.build(),
|
||||
requestURI,
|
||||
loopbackAddr
|
||||
});
|
||||
// anyAddress
|
||||
if (IPSupport.hasIPv6()) {
|
||||
// ipv6 wildcard
|
||||
final var localAddr = InetAddress.getByName("::");
|
||||
testMethodParams.add(new Object[]{
|
||||
newBuilder(configureClientSSL)
|
||||
.localAddress(localAddr)
|
||||
.build(),
|
||||
requestURI,
|
||||
localAddr
|
||||
});
|
||||
}
|
||||
if (IPSupport.hasIPv4()) {
|
||||
// ipv4 wildcard
|
||||
final var localAddr = InetAddress.getByName("0.0.0.0");
|
||||
testMethodParams.add(new Object[]{
|
||||
newBuilder(configureClientSSL)
|
||||
.localAddress(localAddr)
|
||||
.build(),
|
||||
requestURI,
|
||||
localAddr
|
||||
});
|
||||
}
|
||||
}
|
||||
return testMethodParams.stream().toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
private static HttpClient.Builder newBuilder(boolean configureClientSSL) {
|
||||
var builder = HttpClient.newBuilder();
|
||||
// don't let proxies interfere with the client addresses received on the
|
||||
// HTTP request, by the server side handler used in this test.
|
||||
builder.proxy(HttpClient.Builder.NO_PROXY);
|
||||
if (configureClientSSL) {
|
||||
builder.sslContext(sslContext);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request using the {@code client} and expects a 200 response.
|
||||
* The returned response body is then tested to see if the client address
|
||||
* seen by the server side handler is the same one as that is set on the
|
||||
* {@code client}
|
||||
*/
|
||||
@Test(dataProvider = "params")
|
||||
public void testSend(HttpClient client, URI requestURI, InetAddress localAddress) throws Exception {
|
||||
System.out.println("Testing using a HTTP client " + client.version() + " with local address " + localAddress
|
||||
+ " against request URI " + requestURI);
|
||||
// GET request
|
||||
var req = HttpRequest.newBuilder(requestURI).build();
|
||||
var resp = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
|
||||
Assert.assertEquals(resp.statusCode(), 200, "Unexpected status code");
|
||||
// verify the address only if a specific one was set on the client
|
||||
if (localAddress != null && !localAddress.isAnyLocalAddress()) {
|
||||
Assert.assertEquals(resp.body(), localAddress.getAddress(),
|
||||
"Unexpected client address seen by the server handler");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request using the {@code sendAsync} method on the {@code client} and
|
||||
* expects a 200 response. The returned response body is then tested to see if the client address
|
||||
* seen by the server side handler is the same one as that is set on the
|
||||
* {@code client}
|
||||
*/
|
||||
@Test(dataProvider = "params")
|
||||
public void testSendAsync(HttpClient client, URI requestURI, InetAddress localAddress) throws Exception {
|
||||
System.out.println("Testing using a HTTP client " + client.version() + " with local address " + localAddress
|
||||
+ " against request URI " + requestURI);
|
||||
// GET request
|
||||
var req = HttpRequest.newBuilder(requestURI).build();
|
||||
var cf = client.sendAsync(req,
|
||||
HttpResponse.BodyHandlers.ofByteArray());
|
||||
var resp = cf.get();
|
||||
Assert.assertEquals(resp.statusCode(), 200, "Unexpected status code");
|
||||
// verify the address only if a specific one was set on the client
|
||||
if (localAddress != null && !localAddress.isAnyLocalAddress()) {
|
||||
Assert.assertEquals(resp.body(), localAddress.getAddress(),
|
||||
"Unexpected client address seen by the server handler");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the {@link #testSend(HttpClient)} and {@link #testSendAsync(HttpClient)}
|
||||
* tests, concurrently in multiple threads to verify that the correct local address
|
||||
* is used when multiple concurrent threads are involved in sending requests from
|
||||
* the {@code client}
|
||||
*/
|
||||
@Test(dataProvider = "params")
|
||||
public void testMultiSendRequests(HttpClient client, URI requestURI, InetAddress localAddress) throws Exception {
|
||||
int numThreads = 4;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
|
||||
List<Future<Void>> taskResults = new ArrayList<>();
|
||||
try {
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
final var currentIdx = i;
|
||||
var f = executor.submit(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
// test some for send and some for sendAsync
|
||||
if (currentIdx % 2 == 0) {
|
||||
testSend(client, requestURI, localAddress);
|
||||
} else {
|
||||
testSendAsync(client, requestURI, localAddress);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
taskResults.add(f);
|
||||
}
|
||||
// wait for results
|
||||
for (var r : taskResults) {
|
||||
r.get();
|
||||
}
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2022, 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
|
||||
@ -208,6 +208,7 @@ public interface HttpServerAdapters {
|
||||
public abstract URI getRequestURI();
|
||||
public abstract String getRequestMethod();
|
||||
public abstract void close();
|
||||
public abstract InetSocketAddress getRemoteAddress();
|
||||
public void serverPush(URI uri, HttpHeaders headers, byte[] body) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(body);
|
||||
serverPush(uri, headers, bais);
|
||||
@ -265,6 +266,12 @@ public interface HttpServerAdapters {
|
||||
}
|
||||
@Override
|
||||
public void close() { exchange.close(); }
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return exchange.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI() { return exchange.getRequestURI(); }
|
||||
@Override
|
||||
@ -320,6 +327,12 @@ public interface HttpServerAdapters {
|
||||
}
|
||||
@Override
|
||||
public void close() { exchange.close();}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return exchange.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI() { return exchange.getRequestURI(); }
|
||||
@Override
|
||||
|
@ -1 +1,2 @@
|
||||
modules = java.net.http
|
||||
maxOutputSize = 2500000
|
||||
|
@ -357,6 +357,7 @@ public class Http2TestServer implements AutoCloseable {
|
||||
// and if so then the client might wait
|
||||
// forever.
|
||||
System.err.println(name + ": start exception: " + e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.err.println(name + ": stopping is: " + stopping);
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright (c) 2022, 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.
|
||||
//
|
||||
|
||||
// for JTwork/classes/0/test/lib/jdk/test/lib/net/SimpleSSLContext.class and
|
||||
// JTwork/classes/0/test/lib/jdk/test/lib/net/IPSupport.class
|
||||
grant codeBase "file:${test.classes}/../../../../test/lib/-" {
|
||||
permission java.util.PropertyPermission "test.src.path", "read";
|
||||
permission java.io.FilePermission "${test.src}/../../../../lib/jdk/test/lib/net/testkeys", "read";
|
||||
permission java.util.PropertyPermission "java.net.preferIPv4Stack", "read";
|
||||
permission java.util.PropertyPermission "java.net.preferIPv6Addresses", "read";
|
||||
};
|
||||
|
||||
// for JTwork//classes/0/java/net/httpclient/http2/server/*
|
||||
grant codeBase "file:${test.classes}/../../../../java/net/httpclient/http2/server/*" {
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
|
||||
|
||||
permission java.net.SocketPermission "localhost:*", "listen,accept,resolve";
|
||||
permission java.lang.RuntimePermission "modifyThread";
|
||||
};
|
||||
|
||||
grant codeBase "file:${test.classes}/-" {
|
||||
|
||||
// test issues HTTP GET requests
|
||||
permission java.net.URLPermission "http://localhost:*/-", "GET";
|
||||
permission java.net.URLPermission "https://localhost:*/-", "GET";
|
||||
permission java.net.URLPermission "http://localhost:*/-", "GET";
|
||||
permission java.net.URLPermission "https://localhost:*/-", "GET";
|
||||
|
||||
// needed to grant permission to the HTTP/2 server
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
|
||||
|
||||
// for HTTP/1.1 server logging
|
||||
permission java.util.logging.LoggingPermission "control";
|
||||
|
||||
// needed to grant the HTTP servers
|
||||
permission java.net.SocketPermission "localhost:*", "accept,resolve";
|
||||
|
||||
permission java.util.PropertyPermission "*", "read";
|
||||
permission java.lang.RuntimePermission "modifyThread";
|
||||
|
||||
};
|
Loading…
Reference in New Issue
Block a user