mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-22 20:14:43 +02:00
8208391: Differentiate response and connect timeouts in HTTP Client API
Reviewed-by: michaelm
This commit is contained in:
parent
e850549b71
commit
166030817f
32 changed files with 1280 additions and 56 deletions
|
@ -34,6 +34,7 @@ import java.net.ProxySelector;
|
||||||
import java.net.URLPermission;
|
import java.net.URLPermission;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -84,6 +85,7 @@ import jdk.internal.net.http.HttpClientBuilderImpl;
|
||||||
* <pre>{@code HttpClient client = HttpClient.newBuilder()
|
* <pre>{@code HttpClient client = HttpClient.newBuilder()
|
||||||
* .version(Version.HTTP_1_1)
|
* .version(Version.HTTP_1_1)
|
||||||
* .followRedirects(Redirect.NORMAL)
|
* .followRedirects(Redirect.NORMAL)
|
||||||
|
* .connectTimeout(Duration.ofSeconds(20))
|
||||||
* .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
|
* .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
|
||||||
* .authenticator(Authenticator.getDefault())
|
* .authenticator(Authenticator.getDefault())
|
||||||
* .build();
|
* .build();
|
||||||
|
@ -94,7 +96,7 @@ import jdk.internal.net.http.HttpClientBuilderImpl;
|
||||||
* <p><b>Asynchronous Example</b>
|
* <p><b>Asynchronous Example</b>
|
||||||
* <pre>{@code HttpRequest request = HttpRequest.newBuilder()
|
* <pre>{@code HttpRequest request = HttpRequest.newBuilder()
|
||||||
* .uri(URI.create("https://foo.com/"))
|
* .uri(URI.create("https://foo.com/"))
|
||||||
* .timeout(Duration.ofMinutes(1))
|
* .timeout(Duration.ofMinutes(2))
|
||||||
* .header("Content-Type", "application/json")
|
* .header("Content-Type", "application/json")
|
||||||
* .POST(BodyPublishers.ofFile(Paths.get("file.json")))
|
* .POST(BodyPublishers.ofFile(Paths.get("file.json")))
|
||||||
* .build();
|
* .build();
|
||||||
|
@ -196,6 +198,26 @@ public abstract class HttpClient {
|
||||||
*/
|
*/
|
||||||
public Builder cookieHandler(CookieHandler cookieHandler);
|
public Builder cookieHandler(CookieHandler cookieHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the connect timeout duration for this client.
|
||||||
|
*
|
||||||
|
* <p> In the case where a new connection needs to be established, if
|
||||||
|
* the connection cannot be established within the given {@code
|
||||||
|
* duration}, then {@link HttpClient#send(HttpRequest,BodyHandler)
|
||||||
|
* HttpClient::send} throws an {@link HttpConnectTimeoutException}, or
|
||||||
|
* {@link HttpClient#sendAsync(HttpRequest,BodyHandler)
|
||||||
|
* HttpClient::sendAsync} completes exceptionally with an
|
||||||
|
* {@code HttpConnectTimeoutException}. If a new connection does not
|
||||||
|
* need to be established, for example if a connection can be reused
|
||||||
|
* from a previous request, then this timeout duration has no effect.
|
||||||
|
*
|
||||||
|
* @param duration the duration to allow the underlying connection to be
|
||||||
|
* established
|
||||||
|
* @return this builder
|
||||||
|
* @throws IllegalArgumentException if the duration is non-positive
|
||||||
|
*/
|
||||||
|
public Builder connectTimeout(Duration duration);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an {@code SSLContext}.
|
* Sets an {@code SSLContext}.
|
||||||
*
|
*
|
||||||
|
@ -344,6 +366,17 @@ public abstract class HttpClient {
|
||||||
*/
|
*/
|
||||||
public abstract Optional<CookieHandler> cookieHandler();
|
public abstract Optional<CookieHandler> cookieHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@code Optional} containing the <i>connect timeout duration</i>
|
||||||
|
* for this client. If the {@linkplain Builder#connectTimeout(Duration)
|
||||||
|
* connect timeout duration} was not set in the client's builder, then the
|
||||||
|
* {@code Optional} is empty.
|
||||||
|
*
|
||||||
|
* @return an {@code Optional} containing this client's connect timeout
|
||||||
|
* duration
|
||||||
|
*/
|
||||||
|
public abstract Optional<Duration> connectTimeout();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the follow redirects policy for this client. The default value
|
* Returns the follow redirects policy for this client. The default value
|
||||||
* for client's built by builders that do not specify a redirect policy is
|
* for client's built by builders that do not specify a redirect policy is
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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. 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 java.net.http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a connection, over which an {@code HttpRequest} is intended to be
|
||||||
|
* sent, is not successfully established within a specified time period.
|
||||||
|
*
|
||||||
|
* @since 11
|
||||||
|
*/
|
||||||
|
public class HttpConnectTimeoutException extends HttpTimeoutException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 321L + 11L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code HttpConnectTimeoutException} with the given detail
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* The detail message; can be {@code null}
|
||||||
|
*/
|
||||||
|
public HttpConnectTimeoutException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,11 +80,9 @@ abstract class AbstractAsyncSSLConnection extends HttpConnection
|
||||||
engine = createEngine(context, serverName.getName(), port, sslParameters);
|
engine = createEngine(context, serverName.getName(), port, sslParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract HttpConnection plainConnection();
|
|
||||||
abstract SSLTube getConnectionFlow();
|
abstract SSLTube getConnectionFlow();
|
||||||
|
|
||||||
final CompletableFuture<String> getALPN() {
|
final CompletableFuture<String> getALPN() {
|
||||||
assert connected();
|
|
||||||
return getConnectionFlow().getALPN();
|
return getConnectionFlow().getALPN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ package jdk.internal.net.http;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import jdk.internal.net.http.common.MinimalFuture;
|
||||||
import jdk.internal.net.http.common.SSLTube;
|
import jdk.internal.net.http.common.SSLTube;
|
||||||
import jdk.internal.net.http.common.Utils;
|
import jdk.internal.net.http.common.Utils;
|
||||||
|
|
||||||
|
@ -49,14 +51,9 @@ class AsyncSSLConnection extends AbstractAsyncSSLConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
PlainHttpConnection plainConnection() {
|
public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {
|
||||||
return plainConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Void> connectAsync() {
|
|
||||||
return plainConnection
|
return plainConnection
|
||||||
.connectAsync()
|
.connectAsync(exchange)
|
||||||
.thenApply( unused -> {
|
.thenApply( unused -> {
|
||||||
// create the SSLTube wrapping the SocketTube, with the given engine
|
// create the SSLTube wrapping the SocketTube, with the given engine
|
||||||
flow = new SSLTube(engine,
|
flow = new SSLTube(engine,
|
||||||
|
@ -66,6 +63,21 @@ class AsyncSSLConnection extends AbstractAsyncSSLConnection {
|
||||||
return null; } );
|
return null; } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> finishConnect() {
|
||||||
|
// The actual ALPN value, which may be the empty string, is not
|
||||||
|
// interesting at this point, only that the handshake has completed.
|
||||||
|
return getALPN()
|
||||||
|
.handle((String unused, Throwable ex) -> {
|
||||||
|
if (ex == null) {
|
||||||
|
return plainConnection.finishConnect();
|
||||||
|
} else {
|
||||||
|
plainConnection.close();
|
||||||
|
return MinimalFuture.<Void>failedFuture(ex);
|
||||||
|
} })
|
||||||
|
.thenCompose(Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean connected() {
|
boolean connected() {
|
||||||
return plainConnection.connected();
|
return plainConnection.connected();
|
||||||
|
|
|
@ -29,6 +29,8 @@ import java.net.InetSocketAddress;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.net.http.HttpHeaders;
|
import java.net.http.HttpHeaders;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import jdk.internal.net.http.common.MinimalFuture;
|
||||||
import jdk.internal.net.http.common.SSLTube;
|
import jdk.internal.net.http.common.SSLTube;
|
||||||
import jdk.internal.net.http.common.Utils;
|
import jdk.internal.net.http.common.Utils;
|
||||||
|
|
||||||
|
@ -53,13 +55,13 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> connectAsync() {
|
public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {
|
||||||
if (debug.on()) debug.log("Connecting plain tunnel connection");
|
if (debug.on()) debug.log("Connecting plain tunnel connection");
|
||||||
// This will connect the PlainHttpConnection flow, so that
|
// This will connect the PlainHttpConnection flow, so that
|
||||||
// its HttpSubscriber and HttpPublisher are subscribed to the
|
// its HttpSubscriber and HttpPublisher are subscribed to the
|
||||||
// SocketTube
|
// SocketTube
|
||||||
return plainConnection
|
return plainConnection
|
||||||
.connectAsync()
|
.connectAsync(exchange)
|
||||||
.thenApply( unused -> {
|
.thenApply( unused -> {
|
||||||
if (debug.on()) debug.log("creating SSLTube");
|
if (debug.on()) debug.log("creating SSLTube");
|
||||||
// create the SSLTube wrapping the SocketTube, with the given engine
|
// create the SSLTube wrapping the SocketTube, with the given engine
|
||||||
|
@ -70,6 +72,21 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
|
||||||
return null;} );
|
return null;} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> finishConnect() {
|
||||||
|
// The actual ALPN value, which may be the empty string, is not
|
||||||
|
// interesting at this point, only that the handshake has completed.
|
||||||
|
return getALPN()
|
||||||
|
.handle((String unused, Throwable ex) -> {
|
||||||
|
if (ex == null) {
|
||||||
|
return plainConnection.finishConnect();
|
||||||
|
} else {
|
||||||
|
plainConnection.close();
|
||||||
|
return MinimalFuture.<Void>failedFuture(ex);
|
||||||
|
} })
|
||||||
|
.thenCompose(Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isTunnel() { return true; }
|
boolean isTunnel() { return true; }
|
||||||
|
|
||||||
|
@ -86,11 +103,6 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
|
||||||
return "AsyncSSLTunnelConnection: " + super.toString();
|
return "AsyncSSLTunnelConnection: " + super.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
PlainTunnelingConnection plainConnection() {
|
|
||||||
return plainConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ConnectionPool.CacheKey cacheKey() {
|
ConnectionPool.CacheKey cacheKey() {
|
||||||
return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
|
return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
|
||||||
|
|
|
@ -83,6 +83,10 @@ final class Exchange<T> {
|
||||||
final PushGroup<T> pushGroup;
|
final PushGroup<T> pushGroup;
|
||||||
final String dbgTag;
|
final String dbgTag;
|
||||||
|
|
||||||
|
// Keeps track of the underlying connection when establishing an HTTP/2
|
||||||
|
// exchange so that it can be aborted/timed out mid setup.
|
||||||
|
final ConnectionAborter connectionAborter = new ConnectionAborter();
|
||||||
|
|
||||||
Exchange(HttpRequestImpl request, MultiExchange<T> multi) {
|
Exchange(HttpRequestImpl request, MultiExchange<T> multi) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.upgrading = false;
|
this.upgrading = false;
|
||||||
|
@ -125,6 +129,27 @@ final class Exchange<T> {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keeps track of the underlying connection when establishing an HTTP/2
|
||||||
|
// exchange so that it can be aborted/timed out mid setup.
|
||||||
|
static final class ConnectionAborter {
|
||||||
|
private volatile HttpConnection connection;
|
||||||
|
|
||||||
|
void connection(HttpConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeConnection() {
|
||||||
|
HttpConnection connection = this.connection;
|
||||||
|
this.connection = null;
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
connection.close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
|
public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
|
||||||
// If we received a 407 while establishing the exchange
|
// If we received a 407 while establishing the exchange
|
||||||
|
@ -179,6 +204,7 @@ final class Exchange<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel(IOException cause) {
|
public void cancel(IOException cause) {
|
||||||
|
if (debug.on()) debug.log("cancel exchImpl: %s, with \"%s\"", exchImpl, cause);
|
||||||
// If the impl is non null, propagate the exception right away.
|
// If the impl is non null, propagate the exception right away.
|
||||||
// Otherwise record it so that it can be propagated once the
|
// Otherwise record it so that it can be propagated once the
|
||||||
// exchange impl has been established.
|
// exchange impl has been established.
|
||||||
|
@ -190,6 +216,11 @@ final class Exchange<T> {
|
||||||
} else {
|
} else {
|
||||||
// no impl yet. record the exception
|
// no impl yet. record the exception
|
||||||
failed = cause;
|
failed = cause;
|
||||||
|
|
||||||
|
// abort/close the connection if setting up the exchange. This can
|
||||||
|
// be important when setting up HTTP/2
|
||||||
|
connectionAborter.closeConnection();
|
||||||
|
|
||||||
// now call checkCancelled to recheck the impl.
|
// now call checkCancelled to recheck the impl.
|
||||||
// if the failed state is set and the impl is not null, reset
|
// if the failed state is set and the impl is not null, reset
|
||||||
// the failed state and propagate the exception to the impl.
|
// the failed state and propagate the exception to the impl.
|
||||||
|
|
|
@ -85,7 +85,7 @@ abstract class ExchangeImpl<T> {
|
||||||
} else {
|
} else {
|
||||||
Http2ClientImpl c2 = exchange.client().client2(); // #### improve
|
Http2ClientImpl c2 = exchange.client().client2(); // #### improve
|
||||||
HttpRequestImpl request = exchange.request();
|
HttpRequestImpl request = exchange.request();
|
||||||
CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request);
|
CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request, exchange);
|
||||||
if (debug.on())
|
if (debug.on())
|
||||||
debug.log("get: Trying to get HTTP/2 connection");
|
debug.log("get: Trying to get HTTP/2 connection");
|
||||||
return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))
|
return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))
|
||||||
|
|
|
@ -233,7 +233,8 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||||
CompletableFuture<Void> connectCF;
|
CompletableFuture<Void> connectCF;
|
||||||
if (!connection.connected()) {
|
if (!connection.connected()) {
|
||||||
if (debug.on()) debug.log("initiating connect async");
|
if (debug.on()) debug.log("initiating connect async");
|
||||||
connectCF = connection.connectAsync();
|
connectCF = connection.connectAsync(exchange)
|
||||||
|
.thenCompose(unused -> connection.finishConnect());
|
||||||
Throwable cancelled;
|
Throwable cancelled;
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if ((cancelled = failed) == null) {
|
if ((cancelled = failed) == null) {
|
||||||
|
|
|
@ -90,7 +90,8 @@ class Http2ClientImpl {
|
||||||
* 3. completes normally with null: no connection in cache for h2c or h2 failed previously
|
* 3. completes normally with null: no connection in cache for h2c or h2 failed previously
|
||||||
* 4. completes normally with connection: h2 or h2c connection in cache. Use it.
|
* 4. completes normally with connection: h2 or h2c connection in cache. Use it.
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) {
|
CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req,
|
||||||
|
Exchange<?> exchange) {
|
||||||
URI uri = req.uri();
|
URI uri = req.uri();
|
||||||
InetSocketAddress proxy = req.proxy();
|
InetSocketAddress proxy = req.proxy();
|
||||||
String key = Http2Connection.keyFor(uri, proxy);
|
String key = Http2Connection.keyFor(uri, proxy);
|
||||||
|
@ -123,7 +124,7 @@ class Http2ClientImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Http2Connection
|
return Http2Connection
|
||||||
.createAsync(req, this)
|
.createAsync(req, this, exchange)
|
||||||
.whenComplete((conn, t) -> {
|
.whenComplete((conn, t) -> {
|
||||||
synchronized (Http2ClientImpl.this) {
|
synchronized (Http2ClientImpl.this) {
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
|
|
|
@ -353,7 +353,8 @@ class Http2Connection {
|
||||||
|
|
||||||
// Requires TLS handshake. So, is really async
|
// Requires TLS handshake. So, is really async
|
||||||
static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
|
static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
|
||||||
Http2ClientImpl h2client) {
|
Http2ClientImpl h2client,
|
||||||
|
Exchange<?> exchange) {
|
||||||
assert request.secure();
|
assert request.secure();
|
||||||
AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
|
AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
|
||||||
HttpConnection.getConnection(request.getAddress(),
|
HttpConnection.getConnection(request.getAddress(),
|
||||||
|
@ -361,7 +362,12 @@ class Http2Connection {
|
||||||
request,
|
request,
|
||||||
HttpClient.Version.HTTP_2);
|
HttpClient.Version.HTTP_2);
|
||||||
|
|
||||||
return connection.connectAsync()
|
// Expose the underlying connection to the exchange's aborter so it can
|
||||||
|
// be closed if a timeout occurs.
|
||||||
|
exchange.connectionAborter.connection(connection);
|
||||||
|
|
||||||
|
return connection.connectAsync(exchange)
|
||||||
|
.thenCompose(unused -> connection.finishConnect())
|
||||||
.thenCompose(unused -> checkSSLConfig(connection))
|
.thenCompose(unused -> checkSSLConfig(connection))
|
||||||
.thenCompose(notused-> {
|
.thenCompose(notused-> {
|
||||||
CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
|
CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
|
||||||
|
|
|
@ -28,6 +28,7 @@ package jdk.internal.net.http;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
|
@ -38,6 +39,7 @@ import static java.util.Objects.requireNonNull;
|
||||||
public class HttpClientBuilderImpl implements HttpClient.Builder {
|
public class HttpClientBuilderImpl implements HttpClient.Builder {
|
||||||
|
|
||||||
CookieHandler cookieHandler;
|
CookieHandler cookieHandler;
|
||||||
|
Duration connectTimeout;
|
||||||
HttpClient.Redirect followRedirects;
|
HttpClient.Redirect followRedirects;
|
||||||
ProxySelector proxy;
|
ProxySelector proxy;
|
||||||
Authenticator authenticator;
|
Authenticator authenticator;
|
||||||
|
@ -55,6 +57,14 @@ public class HttpClientBuilderImpl implements HttpClient.Builder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClientBuilderImpl connectTimeout(Duration duration) {
|
||||||
|
requireNonNull(duration);
|
||||||
|
if (duration.isNegative() || Duration.ZERO.equals(duration))
|
||||||
|
throw new IllegalArgumentException("Invalid duration: " + duration);
|
||||||
|
this.connectTimeout = duration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
|
public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.lang.ref.Reference;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -69,6 +70,11 @@ final class HttpClientFacade extends HttpClient implements Trackable {
|
||||||
return impl.cookieHandler();
|
return impl.cookieHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Duration> connectTimeout() {
|
||||||
|
return impl.connectTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Redirect followRedirects() {
|
public Redirect followRedirects() {
|
||||||
return impl.followRedirects();
|
return impl.followRedirects();
|
||||||
|
|
|
@ -35,6 +35,7 @@ import java.net.Authenticator;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
import java.net.http.HttpTimeoutException;
|
import java.net.http.HttpTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.CancelledKeyException;
|
import java.nio.channels.CancelledKeyException;
|
||||||
|
@ -47,6 +48,7 @@ import java.security.AccessControlContext;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -154,6 +156,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CookieHandler cookieHandler;
|
private final CookieHandler cookieHandler;
|
||||||
|
private final Duration connectTimeout;
|
||||||
private final Redirect followRedirects;
|
private final Redirect followRedirects;
|
||||||
private final Optional<ProxySelector> userProxySelector;
|
private final Optional<ProxySelector> userProxySelector;
|
||||||
private final ProxySelector proxySelector;
|
private final ProxySelector proxySelector;
|
||||||
|
@ -278,6 +281,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||||
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
|
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
|
||||||
client2 = new Http2ClientImpl(this);
|
client2 = new Http2ClientImpl(this);
|
||||||
cookieHandler = builder.cookieHandler;
|
cookieHandler = builder.cookieHandler;
|
||||||
|
connectTimeout = builder.connectTimeout;
|
||||||
followRedirects = builder.followRedirects == null ?
|
followRedirects = builder.followRedirects == null ?
|
||||||
Redirect.NEVER : builder.followRedirects;
|
Redirect.NEVER : builder.followRedirects;
|
||||||
this.userProxySelector = Optional.ofNullable(builder.proxy);
|
this.userProxySelector = Optional.ofNullable(builder.proxy);
|
||||||
|
@ -547,6 +551,10 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||||
throw new IllegalArgumentException(msg, throwable);
|
throw new IllegalArgumentException(msg, throwable);
|
||||||
} else if (throwable instanceof SecurityException) {
|
} else if (throwable instanceof SecurityException) {
|
||||||
throw new SecurityException(msg, throwable);
|
throw new SecurityException(msg, throwable);
|
||||||
|
} else if (throwable instanceof HttpConnectTimeoutException) {
|
||||||
|
HttpConnectTimeoutException hcte = new HttpConnectTimeoutException(msg);
|
||||||
|
hcte.initCause(throwable);
|
||||||
|
throw hcte;
|
||||||
} else if (throwable instanceof HttpTimeoutException) {
|
} else if (throwable instanceof HttpTimeoutException) {
|
||||||
throw new HttpTimeoutException(msg);
|
throw new HttpTimeoutException(msg);
|
||||||
} else if (throwable instanceof ConnectException) {
|
} else if (throwable instanceof ConnectException) {
|
||||||
|
@ -1123,6 +1131,11 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||||
return Optional.ofNullable(cookieHandler);
|
return Optional.ofNullable(cookieHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Duration> connectTimeout() {
|
||||||
|
return Optional.ofNullable(connectTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ProxySelector> proxy() {
|
public Optional<ProxySelector> proxy() {
|
||||||
return this.userProxySelector;
|
return this.userProxySelector;
|
||||||
|
|
|
@ -108,9 +108,20 @@ abstract class HttpConnection implements Closeable {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public abstract void connect() throws IOException, InterruptedException;
|
/**
|
||||||
|
* Initiates the connect phase.
|
||||||
|
*
|
||||||
|
* Returns a CompletableFuture that completes when the underlying
|
||||||
|
* TCP connection has been established or an error occurs.
|
||||||
|
*/
|
||||||
|
public abstract CompletableFuture<Void> connectAsync(Exchange<?> exchange);
|
||||||
|
|
||||||
public abstract CompletableFuture<Void> connectAsync();
|
/**
|
||||||
|
* Finishes the connection phase.
|
||||||
|
*
|
||||||
|
* Returns a CompletableFuture that completes when any additional,
|
||||||
|
* type specific, setup has been done. Must be called after connectAsync. */
|
||||||
|
public abstract CompletableFuture<Void> finishConnect();
|
||||||
|
|
||||||
/** Tells whether, or not, this connection is connected to its destination. */
|
/** Tells whether, or not, this connection is connected to its destination. */
|
||||||
abstract boolean connected();
|
abstract boolean connected();
|
||||||
|
|
|
@ -27,7 +27,7 @@ package jdk.internal.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.time.Duration;
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
|
@ -88,7 +88,7 @@ class MultiExchange<T> {
|
||||||
);
|
);
|
||||||
|
|
||||||
private final LinkedList<HeaderFilter> filters;
|
private final LinkedList<HeaderFilter> filters;
|
||||||
TimedEvent timedEvent;
|
ResponseTimerEvent responseTimerEvent;
|
||||||
volatile boolean cancelled;
|
volatile boolean cancelled;
|
||||||
final PushGroup<T> pushGroup;
|
final PushGroup<T> pushGroup;
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ class MultiExchange<T> {
|
||||||
this.exchange = new Exchange<>(request, this);
|
this.exchange = new Exchange<>(request, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Exchange<T> getExchange() {
|
synchronized Exchange<T> getExchange() {
|
||||||
return exchange;
|
return exchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,8 +157,8 @@ class MultiExchange<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelTimer() {
|
private void cancelTimer() {
|
||||||
if (timedEvent != null) {
|
if (responseTimerEvent != null) {
|
||||||
client.cancelTimer(timedEvent);
|
client.cancelTimer(responseTimerEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,8 +220,8 @@ class MultiExchange<T> {
|
||||||
cf = failedFuture(new IOException("Too many retries", retryCause));
|
cf = failedFuture(new IOException("Too many retries", retryCause));
|
||||||
} else {
|
} else {
|
||||||
if (currentreq.timeout().isPresent()) {
|
if (currentreq.timeout().isPresent()) {
|
||||||
timedEvent = new TimedEvent(currentreq.timeout().get());
|
responseTimerEvent = ResponseTimerEvent.of(this);
|
||||||
client.registerTimer(timedEvent);
|
client.registerTimer(responseTimerEvent);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// 1. apply request filters
|
// 1. apply request filters
|
||||||
|
@ -344,7 +344,9 @@ class MultiExchange<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cancelled && t instanceof IOException) {
|
if (cancelled && t instanceof IOException) {
|
||||||
t = new HttpTimeoutException("request timed out");
|
if (!(t instanceof HttpTimeoutException)) {
|
||||||
|
t = toTimeoutException((IOException)t);
|
||||||
|
}
|
||||||
} else if (retryOnFailure(t)) {
|
} else if (retryOnFailure(t)) {
|
||||||
Throwable cause = retryCause(t);
|
Throwable cause = retryCause(t);
|
||||||
|
|
||||||
|
@ -378,17 +380,24 @@ class MultiExchange<T> {
|
||||||
return failedFuture(t);
|
return failedFuture(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimedEvent extends TimeoutEvent {
|
private HttpTimeoutException toTimeoutException(IOException ioe) {
|
||||||
TimedEvent(Duration duration) {
|
HttpTimeoutException t = null;
|
||||||
super(duration);
|
|
||||||
}
|
// more specific, "request timed out", when connected
|
||||||
@Override
|
Exchange<?> exchange = getExchange();
|
||||||
public void handle() {
|
if (exchange != null) {
|
||||||
if (debug.on()) {
|
ExchangeImpl<?> exchangeImpl = exchange.exchImpl;
|
||||||
debug.log("Cancelling MultiExchange due to timeout for request %s",
|
if (exchangeImpl != null) {
|
||||||
request);
|
if (exchangeImpl.connection().connected()) {
|
||||||
|
t = new HttpTimeoutException("request timed out");
|
||||||
|
t.initCause(ioe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cancel(new HttpTimeoutException("request timed out"));
|
|
||||||
}
|
}
|
||||||
|
if (t == null) {
|
||||||
|
t = new HttpConnectTimeoutException("HTTP connect timed out");
|
||||||
|
t.initCause(new ConnectException("HTTP connect timed out"));
|
||||||
|
}
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
package jdk.internal.net.http;
|
package jdk.internal.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.StandardSocketOptions;
|
import java.net.StandardSocketOptions;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
|
@ -34,6 +35,7 @@ import java.nio.channels.SocketChannel;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedActionException;
|
import java.security.PrivilegedActionException;
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import jdk.internal.net.http.common.FlowTube;
|
import jdk.internal.net.http.common.FlowTube;
|
||||||
import jdk.internal.net.http.common.Log;
|
import jdk.internal.net.http.common.Log;
|
||||||
|
@ -53,9 +55,52 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
private final PlainHttpPublisher writePublisher = new PlainHttpPublisher(reading);
|
private final PlainHttpPublisher writePublisher = new PlainHttpPublisher(reading);
|
||||||
private volatile boolean connected;
|
private volatile boolean connected;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
private volatile ConnectTimerEvent connectTimerEvent; // may be null
|
||||||
|
|
||||||
// should be volatile to provide proper synchronization(visibility) action
|
// should be volatile to provide proper synchronization(visibility) action
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ConnectTimerEvent iff there is a connect timeout duration,
|
||||||
|
* otherwise null.
|
||||||
|
*/
|
||||||
|
private ConnectTimerEvent newConnectTimer(Exchange<?> exchange,
|
||||||
|
CompletableFuture<Void> cf) {
|
||||||
|
Duration duration = client().connectTimeout().orElse(null);
|
||||||
|
if (duration != null) {
|
||||||
|
ConnectTimerEvent cte = new ConnectTimerEvent(duration, exchange, cf);
|
||||||
|
return cte;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ConnectTimerEvent extends TimeoutEvent {
|
||||||
|
private final CompletableFuture<Void> cf;
|
||||||
|
private final Exchange<?> exchange;
|
||||||
|
|
||||||
|
ConnectTimerEvent(Duration duration,
|
||||||
|
Exchange<?> exchange,
|
||||||
|
CompletableFuture<Void> cf) {
|
||||||
|
super(duration);
|
||||||
|
this.exchange = exchange;
|
||||||
|
this.cf = cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() {
|
||||||
|
if (debug.on()) {
|
||||||
|
debug.log("HTTP connect timed out");
|
||||||
|
}
|
||||||
|
ConnectException ce = new ConnectException("HTTP connect timed out");
|
||||||
|
exchange.multi.cancel(ce);
|
||||||
|
client().theExecutor().execute(() -> cf.completeExceptionally(ce));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ConnectTimerEvent, " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ConnectEvent extends AsyncEvent {
|
final class ConnectEvent extends AsyncEvent {
|
||||||
private final CompletableFuture<Void> cf;
|
private final CompletableFuture<Void> cf;
|
||||||
|
|
||||||
|
@ -85,7 +130,6 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
if (debug.on())
|
if (debug.on())
|
||||||
debug.log("ConnectEvent: connect finished: %s Local addr: %s",
|
debug.log("ConnectEvent: connect finished: %s Local addr: %s",
|
||||||
finished, chan.getLocalAddress());
|
finished, chan.getLocalAddress());
|
||||||
connected = true;
|
|
||||||
// complete async since the event runs on the SelectorManager thread
|
// complete async since the event runs on the SelectorManager thread
|
||||||
cf.completeAsync(() -> null, client().theExecutor());
|
cf.completeAsync(() -> null, client().theExecutor());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -103,12 +147,20 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> connectAsync() {
|
public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
CompletableFuture<Void> cf = new MinimalFuture<>();
|
||||||
try {
|
try {
|
||||||
assert !connected : "Already connected";
|
assert !connected : "Already connected";
|
||||||
assert !chan.isBlocking() : "Unexpected blocking channel";
|
assert !chan.isBlocking() : "Unexpected blocking channel";
|
||||||
boolean finished = false;
|
boolean finished;
|
||||||
|
|
||||||
|
connectTimerEvent = newConnectTimer(exchange, cf);
|
||||||
|
if (connectTimerEvent != null) {
|
||||||
|
if (debug.on())
|
||||||
|
debug.log("registering connect timer: " + connectTimerEvent);
|
||||||
|
client().registerTimer(connectTimerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
PrivilegedExceptionAction<Boolean> pa =
|
PrivilegedExceptionAction<Boolean> pa =
|
||||||
() -> chan.connect(Utils.resolveAddress(address));
|
() -> chan.connect(Utils.resolveAddress(address));
|
||||||
try {
|
try {
|
||||||
|
@ -118,7 +170,6 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
}
|
}
|
||||||
if (finished) {
|
if (finished) {
|
||||||
if (debug.on()) debug.log("connect finished without blocking");
|
if (debug.on()) debug.log("connect finished without blocking");
|
||||||
connected = true;
|
|
||||||
cf.complete(null);
|
cf.complete(null);
|
||||||
} else {
|
} else {
|
||||||
if (debug.on()) debug.log("registering connect event");
|
if (debug.on()) debug.log("registering connect event");
|
||||||
|
@ -136,6 +187,16 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> finishConnect() {
|
||||||
|
assert connected == false;
|
||||||
|
if (debug.on()) debug.log("finishConnect, setting connected=true");
|
||||||
|
connected = true;
|
||||||
|
if (connectTimerEvent != null)
|
||||||
|
client().cancelTimer(connectTimerEvent);
|
||||||
|
return MinimalFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
SocketChannel channel() {
|
SocketChannel channel() {
|
||||||
return chan;
|
return chan;
|
||||||
|
@ -210,6 +271,8 @@ class PlainHttpConnection extends HttpConnection {
|
||||||
Log.logTrace("Closing: " + toString());
|
Log.logTrace("Closing: " + toString());
|
||||||
if (debug.on())
|
if (debug.on())
|
||||||
debug.log("Closing channel: " + client().debugInterestOps(chan));
|
debug.log("Closing channel: " + client().debugInterestOps(chan));
|
||||||
|
if (connectTimerEvent != null)
|
||||||
|
client().cancelTimer(connectTimerEvent);
|
||||||
chan.close();
|
chan.close();
|
||||||
tube.signalClosed();
|
tube.signalClosed();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -26,11 +26,13 @@
|
||||||
package jdk.internal.net.http;
|
package jdk.internal.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.System.Logger.Level;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.http.HttpTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.net.http.HttpHeaders;
|
import java.net.http.HttpHeaders;
|
||||||
import jdk.internal.net.http.common.FlowTube;
|
import jdk.internal.net.http.common.FlowTube;
|
||||||
|
@ -60,9 +62,10 @@ final class PlainTunnelingConnection extends HttpConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> connectAsync() {
|
public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {
|
||||||
if (debug.on()) debug.log("Connecting plain connection");
|
if (debug.on()) debug.log("Connecting plain connection");
|
||||||
return delegate.connectAsync()
|
return delegate.connectAsync(exchange)
|
||||||
|
.thenCompose(unused -> delegate.finishConnect())
|
||||||
.thenCompose((Void v) -> {
|
.thenCompose((Void v) -> {
|
||||||
if (debug.on()) debug.log("sending HTTP/1.1 CONNECT");
|
if (debug.on()) debug.log("sending HTTP/1.1 CONNECT");
|
||||||
HttpClientImpl client = client();
|
HttpClientImpl client = client();
|
||||||
|
@ -70,7 +73,7 @@ final class PlainTunnelingConnection extends HttpConnection {
|
||||||
HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
|
HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
|
||||||
MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
|
MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
|
||||||
client, discarding(), null, null);
|
client, discarding(), null, null);
|
||||||
Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
|
Exchange<Void> connectExchange = mulEx.getExchange();
|
||||||
|
|
||||||
return connectExchange
|
return connectExchange
|
||||||
.responseAsyncImpl(delegate)
|
.responseAsyncImpl(delegate)
|
||||||
|
@ -96,14 +99,36 @@ final class PlainTunnelingConnection extends HttpConnection {
|
||||||
ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
|
ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
|
||||||
int remaining = b.remaining();
|
int remaining = b.remaining();
|
||||||
assert remaining == 0: "Unexpected remaining: " + remaining;
|
assert remaining == 0: "Unexpected remaining: " + remaining;
|
||||||
connected = true;
|
|
||||||
cf.complete(null);
|
cf.complete(null);
|
||||||
}
|
}
|
||||||
return cf;
|
return cf;
|
||||||
});
|
})
|
||||||
|
.handle((result, ex) -> {
|
||||||
|
if (ex == null) {
|
||||||
|
return MinimalFuture.completedFuture(result);
|
||||||
|
} else {
|
||||||
|
if (debug.on())
|
||||||
|
debug.log("tunnel failed with \"%s\"", ex.toString());
|
||||||
|
Throwable t = ex;
|
||||||
|
if (t instanceof CompletionException)
|
||||||
|
t = t.getCause();
|
||||||
|
if (t instanceof HttpTimeoutException) {
|
||||||
|
String msg = "proxy tunneling CONNECT request timed out";
|
||||||
|
t = new HttpTimeoutException(msg);
|
||||||
|
t.initCause(ex);
|
||||||
|
}
|
||||||
|
return MinimalFuture.<Void>failedFuture(t);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenCompose(Function.identity());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> finishConnect() {
|
||||||
|
connected = true;
|
||||||
|
return MinimalFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isTunnel() { return true; }
|
boolean isTunnel() { return true; }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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. 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 jdk.internal.net.http;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
|
import java.net.http.HttpTimeoutException;
|
||||||
|
import jdk.internal.net.http.common.Logger;
|
||||||
|
import jdk.internal.net.http.common.Utils;
|
||||||
|
|
||||||
|
public class ResponseTimerEvent extends TimeoutEvent {
|
||||||
|
private static final Logger debug =
|
||||||
|
Utils.getDebugLogger("ResponseTimerEvent"::toString, Utils.DEBUG);
|
||||||
|
|
||||||
|
private final MultiExchange<?> multiExchange;
|
||||||
|
|
||||||
|
static ResponseTimerEvent of(MultiExchange<?> exchange) {
|
||||||
|
return new ResponseTimerEvent(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseTimerEvent(MultiExchange<?> multiExchange) {
|
||||||
|
super(multiExchange.exchange.request.timeout().get());
|
||||||
|
this.multiExchange = multiExchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() {
|
||||||
|
if (debug.on()) {
|
||||||
|
debug.log("Cancelling MultiExchange due to timeout for request %s",
|
||||||
|
multiExchange.exchange.request);
|
||||||
|
}
|
||||||
|
HttpTimeoutException t = null;
|
||||||
|
|
||||||
|
// more specific, "request timed out", message when connected
|
||||||
|
Exchange<?> exchange = multiExchange.getExchange();
|
||||||
|
if (exchange != null) {
|
||||||
|
ExchangeImpl<?> exchangeImpl = exchange.exchImpl;
|
||||||
|
if (exchangeImpl != null) {
|
||||||
|
if (exchangeImpl.connection().connected()) {
|
||||||
|
t = new HttpTimeoutException("request timed out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t == null) {
|
||||||
|
t = new HttpConnectTimeoutException("HTTP connect timed out");
|
||||||
|
t.initCause(new ConnectException("HTTP connect timed out"));
|
||||||
|
}
|
||||||
|
multiExchange.cancel(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ResponseTimerEvent[" + super.toString() + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,9 +43,11 @@ abstract class TimeoutEvent implements Comparable<TimeoutEvent> {
|
||||||
// we use id in compareTo to make compareTo consistent with equals
|
// we use id in compareTo to make compareTo consistent with equals
|
||||||
// see TimeoutEvent::compareTo below;
|
// see TimeoutEvent::compareTo below;
|
||||||
private final long id = COUNTER.incrementAndGet();
|
private final long id = COUNTER.incrementAndGet();
|
||||||
|
private final Duration duration;
|
||||||
private final Instant deadline;
|
private final Instant deadline;
|
||||||
|
|
||||||
TimeoutEvent(Duration duration) {
|
TimeoutEvent(Duration duration) {
|
||||||
|
this.duration = duration;
|
||||||
deadline = Instant.now().plus(duration);
|
deadline = Instant.now().plus(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +77,7 @@ abstract class TimeoutEvent implements Comparable<TimeoutEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TimeoutEvent[id=" + id + ", deadline=" + deadline + "]";
|
return "TimeoutEvent[id=" + id + ", duration=" + duration
|
||||||
|
+ ", deadline=" + deadline + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
257
test/jdk/java/net/httpclient/AbstractConnectTimeout.java
Normal file
257
test/jdk/java/net/httpclient/AbstractConnectTimeout.java
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpRequest.BodyPublishers;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import static java.lang.System.out;
|
||||||
|
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||||
|
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||||
|
import static java.net.http.HttpClient.Version.HTTP_2;
|
||||||
|
import static java.time.Duration.*;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
public abstract class AbstractConnectTimeout {
|
||||||
|
|
||||||
|
static final Duration NO_DURATION = null;
|
||||||
|
|
||||||
|
static List<List<Duration>> TIMEOUTS = List.of(
|
||||||
|
// connectTimeout HttpRequest timeout
|
||||||
|
Arrays.asList( NO_DURATION, ofSeconds(1) ),
|
||||||
|
Arrays.asList( NO_DURATION, ofMillis(100) ),
|
||||||
|
Arrays.asList( NO_DURATION, ofNanos(99) ),
|
||||||
|
Arrays.asList( NO_DURATION, ofNanos(1) ),
|
||||||
|
|
||||||
|
Arrays.asList( ofSeconds(1), NO_DURATION ),
|
||||||
|
Arrays.asList( ofMillis(100), NO_DURATION ),
|
||||||
|
Arrays.asList( ofNanos(99), NO_DURATION ),
|
||||||
|
Arrays.asList( ofNanos(1), NO_DURATION ),
|
||||||
|
|
||||||
|
Arrays.asList( ofSeconds(1), ofMinutes(1) ),
|
||||||
|
Arrays.asList( ofMillis(100), ofMinutes(1) ),
|
||||||
|
Arrays.asList( ofNanos(99), ofMinutes(1) ),
|
||||||
|
Arrays.asList( ofNanos(1), ofMinutes(1) )
|
||||||
|
);
|
||||||
|
|
||||||
|
static final List<String> METHODS = List.of("GET", "POST");
|
||||||
|
static final List<Version> VERSIONS = List.of(HTTP_2, HTTP_1_1);
|
||||||
|
static final List<String> SCHEMES = List.of("https", "http");
|
||||||
|
|
||||||
|
@DataProvider(name = "variants")
|
||||||
|
public Object[][] variants() {
|
||||||
|
List<Object[]> l = new ArrayList<>();
|
||||||
|
for (List<Duration> timeouts : TIMEOUTS) {
|
||||||
|
Duration connectTimeout = timeouts.get(0);
|
||||||
|
Duration requestTimeout = timeouts.get(1);
|
||||||
|
for (String method: METHODS) {
|
||||||
|
for (String scheme : SCHEMES) {
|
||||||
|
for (Version requestVersion : VERSIONS) {
|
||||||
|
l.add(new Object[] {requestVersion, scheme, method, connectTimeout, requestTimeout});
|
||||||
|
}}}}
|
||||||
|
return l.stream().toArray(Object[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final ProxySelector EXAMPLE_DOT_COM_PROXY = ProxySelector.of(
|
||||||
|
InetSocketAddress.createUnresolved("example.com", 8080));
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutNoProxySync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutWithProxySync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timeoutSync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout,
|
||||||
|
ProxySelector proxy)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
out.printf("%ntimeoutSync(requestVersion=%s, scheme=%s, method=%s,"
|
||||||
|
+ " connectTimeout=%s, requestTimeout=%s, proxy=%s)%n",
|
||||||
|
requestVersion, scheme, method, connectTimeout, requestTimeout, proxy);
|
||||||
|
|
||||||
|
HttpClient client = newClient(connectTimeout, proxy);
|
||||||
|
HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout);
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
out.printf("iteration %d%n", i);
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
try {
|
||||||
|
HttpResponse<?> resp = client.send(request, BodyHandlers.ofString());
|
||||||
|
printResponse(resp);
|
||||||
|
fail("Unexpected response: " + resp);
|
||||||
|
} catch (HttpConnectTimeoutException expected) { // blocking thread-specific exception
|
||||||
|
long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
out.printf("Client: received in %d millis%n", elapsedTime);
|
||||||
|
assertExceptionTypeAndCause(expected.getCause());
|
||||||
|
} catch (ConnectException e) {
|
||||||
|
long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
out.printf("Client: received in %d millis%n", elapsedTime);
|
||||||
|
Throwable t = e.getCause().getCause(); // blocking thread-specific exception
|
||||||
|
if (!(t instanceof NoRouteToHostException)) { // tolerate only NRTHE
|
||||||
|
e.printStackTrace(out);
|
||||||
|
fail("Unexpected exception:" + e);
|
||||||
|
} else {
|
||||||
|
out.printf("Caught ConnectException with NoRouteToHostException"
|
||||||
|
+ " cause: %s - skipping%n", t.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutNoProxyAsync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutWithProxyAsync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timeoutAsync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout,
|
||||||
|
ProxySelector proxy) {
|
||||||
|
out.printf("%ntimeoutAsync(requestVersion=%s, scheme=%s, method=%s, "
|
||||||
|
+ "connectTimeout=%s, requestTimeout=%s, proxy=%s)%n",
|
||||||
|
requestVersion, scheme, method, connectTimeout, requestTimeout, proxy);
|
||||||
|
|
||||||
|
HttpClient client = newClient(connectTimeout, proxy);
|
||||||
|
HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout);
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
out.printf("iteration %d%n", i);
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
try {
|
||||||
|
HttpResponse<?> resp = client.sendAsync(request, BodyHandlers.ofString()).join();
|
||||||
|
printResponse(resp);
|
||||||
|
fail("Unexpected response: " + resp);
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
out.printf("Client: received in %d millis%n", elapsedTime);
|
||||||
|
Throwable t = e.getCause();
|
||||||
|
if (t instanceof ConnectException &&
|
||||||
|
t.getCause() instanceof NoRouteToHostException) { // tolerate only NRTHE
|
||||||
|
out.printf("Caught ConnectException with NoRouteToHostException"
|
||||||
|
+ " cause: %s - skipping%n", t.getCause());
|
||||||
|
} else {
|
||||||
|
assertExceptionTypeAndCause(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpClient newClient(Duration connectTimeout, ProxySelector proxy) {
|
||||||
|
HttpClient.Builder builder = HttpClient.newBuilder().proxy(proxy);
|
||||||
|
if (connectTimeout != NO_DURATION)
|
||||||
|
builder.connectTimeout(connectTimeout);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpRequest newRequest(String scheme,
|
||||||
|
Version reqVersion,
|
||||||
|
String method,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
// Resolvable address. Most tested environments just ignore the TCP SYN,
|
||||||
|
// or occasionally return ICMP no route to host
|
||||||
|
URI uri = URI.create(scheme +"://example.com:81/");
|
||||||
|
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(uri);
|
||||||
|
reqBuilder = reqBuilder.version(reqVersion);
|
||||||
|
switch (method) {
|
||||||
|
case "GET" : reqBuilder.GET(); break;
|
||||||
|
case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break;
|
||||||
|
default: throw new AssertionError("Unknown method:" + method);
|
||||||
|
}
|
||||||
|
if (requestTimeout != NO_DURATION)
|
||||||
|
reqBuilder.timeout(requestTimeout);
|
||||||
|
return reqBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertExceptionTypeAndCause(Throwable t) {
|
||||||
|
if (!(t instanceof HttpConnectTimeoutException)) {
|
||||||
|
t.printStackTrace(out);
|
||||||
|
fail("Expected HttpConnectTimeoutException, got:" + t);
|
||||||
|
}
|
||||||
|
Throwable connEx = t.getCause();
|
||||||
|
if (!(connEx instanceof ConnectException)) {
|
||||||
|
t.printStackTrace(out);
|
||||||
|
fail("Expected ConnectException cause in:" + connEx);
|
||||||
|
}
|
||||||
|
out.printf("Caught expected HttpConnectTimeoutException with ConnectException"
|
||||||
|
+ " cause: %n%s%n%s%n", t, connEx);
|
||||||
|
final String EXPECTED_MESSAGE = "HTTP connect timed out"; // impl dependent
|
||||||
|
if (!connEx.getMessage().equals(EXPECTED_MESSAGE))
|
||||||
|
fail("Expected: \"" + EXPECTED_MESSAGE + "\", got: \"" + connEx.getMessage() + "\"");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printResponse(HttpResponse<?> response) {
|
||||||
|
out.println("Unexpected response: " + response);
|
||||||
|
out.println("Headers: " + response.headers());
|
||||||
|
out.println("Body: " + response.body());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,288 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpRequest.BodyPublishers;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import org.testng.annotations.AfterTest;
|
||||||
|
import org.testng.annotations.BeforeTest;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.lang.System.out;
|
||||||
|
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||||
|
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||||
|
import static java.net.http.HttpClient.Version.HTTP_2;
|
||||||
|
import static java.time.Duration.*;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
public abstract class AbstractConnectTimeoutHandshake {
|
||||||
|
|
||||||
|
// The number of iterations each testXXXClient performs.
|
||||||
|
static final int TIMES = 2;
|
||||||
|
|
||||||
|
Server server;
|
||||||
|
URI httpsURI;
|
||||||
|
|
||||||
|
static final Duration NO_DURATION = null;
|
||||||
|
|
||||||
|
static List<List<Duration>> TIMEOUTS = List.of(
|
||||||
|
// connectTimeout HttpRequest timeout
|
||||||
|
Arrays.asList( NO_DURATION, ofSeconds(1) ),
|
||||||
|
Arrays.asList( NO_DURATION, ofSeconds(2) ),
|
||||||
|
Arrays.asList( NO_DURATION, ofMillis(500) ),
|
||||||
|
|
||||||
|
Arrays.asList( ofSeconds(1), NO_DURATION ),
|
||||||
|
Arrays.asList( ofSeconds(2), NO_DURATION ),
|
||||||
|
Arrays.asList( ofMillis(500), NO_DURATION ),
|
||||||
|
|
||||||
|
Arrays.asList( ofSeconds(1), ofMinutes(1) ),
|
||||||
|
Arrays.asList( ofSeconds(2), ofMinutes(1) ),
|
||||||
|
Arrays.asList( ofMillis(500), ofMinutes(1) )
|
||||||
|
);
|
||||||
|
|
||||||
|
static final List<String> METHODS = List.of("GET" , "POST");
|
||||||
|
static final List<Version> VERSIONS = List.of(HTTP_2, HTTP_1_1);
|
||||||
|
|
||||||
|
@DataProvider(name = "variants")
|
||||||
|
public Object[][] variants() {
|
||||||
|
List<Object[]> l = new ArrayList<>();
|
||||||
|
for (List<Duration> timeouts : TIMEOUTS) {
|
||||||
|
Duration connectTimeout = timeouts.get(0);
|
||||||
|
Duration requestTimeout = timeouts.get(1);
|
||||||
|
for (String method: METHODS) {
|
||||||
|
for (Version requestVersion : VERSIONS) {
|
||||||
|
l.add(new Object[] {requestVersion, method, connectTimeout, requestTimeout});
|
||||||
|
}}}
|
||||||
|
return l.stream().toArray(Object[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutSync(Version requestVersion,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
out.printf("%n--- timeoutSync requestVersion=%s, method=%s, "
|
||||||
|
+ "connectTimeout=%s, requestTimeout=%s ---%n",
|
||||||
|
requestVersion, method, connectTimeout, requestTimeout);
|
||||||
|
HttpClient client = newClient(connectTimeout);
|
||||||
|
HttpRequest request = newRequest(requestVersion, method, requestTimeout);
|
||||||
|
|
||||||
|
for (int i = 0; i < TIMES; i++) {
|
||||||
|
out.printf("iteration %d%n", i);
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
try {
|
||||||
|
HttpResponse<String> resp = client.send(request, BodyHandlers.ofString());
|
||||||
|
printResponse(resp);
|
||||||
|
fail("Unexpected response: " + resp);
|
||||||
|
} catch (HttpConnectTimeoutException expected) {
|
||||||
|
long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
out.printf("Client: received in %d millis%n", elapsedTime);
|
||||||
|
out.printf("Client: caught expected HttpConnectTimeoutException: %s%n", expected);
|
||||||
|
checkExceptionOrCause(ConnectException.class, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test(dataProvider = "variants")
|
||||||
|
protected void timeoutAsync(Version requestVersion,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
out.printf("%n--- timeoutAsync requestVersion=%s, method=%s, "
|
||||||
|
+ "connectTimeout=%s, requestTimeout=%s ---%n",
|
||||||
|
requestVersion, method, connectTimeout, requestTimeout);
|
||||||
|
HttpClient client = newClient(connectTimeout);
|
||||||
|
HttpRequest request = newRequest(requestVersion, method, requestTimeout);
|
||||||
|
|
||||||
|
for (int i = 0; i < TIMES; i++) {
|
||||||
|
out.printf("iteration %d%n", i);
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
CompletableFuture<HttpResponse<String>> cf =
|
||||||
|
client.sendAsync(request, BodyHandlers.ofString());
|
||||||
|
try {
|
||||||
|
HttpResponse<String> resp = cf.join();
|
||||||
|
printResponse(resp);
|
||||||
|
fail("Unexpected response: " + resp);
|
||||||
|
} catch (CompletionException ce) {
|
||||||
|
long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
out.printf("Client: received in %d millis%n", elapsedTime);
|
||||||
|
Throwable expected = ce.getCause();
|
||||||
|
if (expected instanceof HttpConnectTimeoutException) {
|
||||||
|
out.printf("Client: caught expected HttpConnectTimeoutException: %s%n", expected);
|
||||||
|
checkExceptionOrCause(ConnectException.class, expected);
|
||||||
|
} else {
|
||||||
|
out.printf("Client: caught UNEXPECTED exception: %s%n", expected);
|
||||||
|
throw ce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpClient newClient(Duration connectTimeout) {
|
||||||
|
HttpClient.Builder builder = HttpClient.newBuilder().proxy(NO_PROXY);
|
||||||
|
if (connectTimeout != NO_DURATION)
|
||||||
|
builder.connectTimeout(connectTimeout);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest newRequest(Version reqVersion,
|
||||||
|
String method,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(httpsURI);
|
||||||
|
reqBuilder = reqBuilder.version(reqVersion);
|
||||||
|
switch (method) {
|
||||||
|
case "GET" : reqBuilder.GET(); break;
|
||||||
|
case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break;
|
||||||
|
default: throw new AssertionError("Unknown method:" + method);
|
||||||
|
}
|
||||||
|
if (requestTimeout != NO_DURATION)
|
||||||
|
reqBuilder.timeout(requestTimeout);
|
||||||
|
return reqBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkExceptionOrCause(Class<? extends Throwable> clazz, Throwable t) {
|
||||||
|
final Throwable original = t;
|
||||||
|
do {
|
||||||
|
if (clazz.isInstance(t)) {
|
||||||
|
System.out.println("Found expected exception/cause: " + t);
|
||||||
|
return; // found
|
||||||
|
}
|
||||||
|
} while ((t = t.getCause()) != null);
|
||||||
|
original.printStackTrace(System.out);
|
||||||
|
throw new RuntimeException("Expected " + clazz + "in " + original);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printResponse(HttpResponse<?> response) {
|
||||||
|
out.println("Unexpected response: " + response);
|
||||||
|
out.println("Headers: " + response.headers());
|
||||||
|
out.println("Body: " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Infrastructure
|
||||||
|
|
||||||
|
static String serverAuthority(Server server) {
|
||||||
|
return InetAddress.getLoopbackAddress().getHostName() + ":"
|
||||||
|
+ server.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeTest
|
||||||
|
public void setup() throws Exception {
|
||||||
|
server = new Server();
|
||||||
|
httpsURI = URI.create("https://" + serverAuthority(server) + "/foo");
|
||||||
|
out.println("HTTPS URI: " + httpsURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterTest
|
||||||
|
public void teardown() throws Exception {
|
||||||
|
server.close();
|
||||||
|
out.printf("%n--- teardown ---%n");
|
||||||
|
|
||||||
|
int numClientConnections = variants().length * TIMES;
|
||||||
|
int serverCount = server.count;
|
||||||
|
out.printf("Client made %d connections.%n", numClientConnections);
|
||||||
|
out.printf("Server received %d connections.%n", serverCount);
|
||||||
|
|
||||||
|
// This is usually the case, but not always, do not assert. Remains
|
||||||
|
// as an informative message only.
|
||||||
|
//if (numClientConnections != serverCount)
|
||||||
|
// fail(format("[numTests: %d] != [serverCount: %d]",
|
||||||
|
// numClientConnections, serverCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates a server-side, using plain cleartext Sockets, that just reads
|
||||||
|
* initial client hello and does nothing more.
|
||||||
|
*/
|
||||||
|
static class Server extends Thread implements AutoCloseable {
|
||||||
|
private final ServerSocket ss;
|
||||||
|
private volatile boolean closed;
|
||||||
|
volatile int count;
|
||||||
|
|
||||||
|
Server() throws IOException {
|
||||||
|
super("Server");
|
||||||
|
ss = new ServerSocket();
|
||||||
|
ss.setReuseAddress(false);
|
||||||
|
ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getPort() {
|
||||||
|
return ss.getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed)
|
||||||
|
return;
|
||||||
|
closed = true;
|
||||||
|
try {
|
||||||
|
ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Unexpected", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!closed) {
|
||||||
|
try (Socket s = ss.accept()) {
|
||||||
|
count++;
|
||||||
|
out.println("Server: accepted new connection");
|
||||||
|
InputStream is = new BufferedInputStream(s.getInputStream());
|
||||||
|
|
||||||
|
out.println("Server: starting to read");
|
||||||
|
while (is.read() != -1) ;
|
||||||
|
|
||||||
|
out.println("Server: closing connection");
|
||||||
|
s.close(); // close without giving any reply
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (!closed)
|
||||||
|
out.println("UNEXPECTED " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests connection timeouts during SSL handshake
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutHandshakeAsync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutHandshakeAsync
|
||||||
|
extends AbstractConnectTimeoutHandshake
|
||||||
|
{
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutAsync(Version requestVersion,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout) {
|
||||||
|
super.timeoutAsync(requestVersion, method, connectTimeout, requestTimeout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests connection timeouts during SSL handshake
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutHandshakeSync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutHandshakeSync
|
||||||
|
extends AbstractConnectTimeoutHandshake
|
||||||
|
{
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutSync(Version requestVersion,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
super.timeoutSync(requestVersion, method, connectTimeout, requestTimeout);
|
||||||
|
}
|
||||||
|
}
|
47
test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java
Normal file
47
test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests for connection related timeouts
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutNoProxyAsync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutNoProxyAsync extends AbstractConnectTimeout {
|
||||||
|
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutNoProxyAsync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestduration)
|
||||||
|
{
|
||||||
|
super.timeoutNoProxyAsync(requestVersion, scheme, method, connectTimeout, requestduration);
|
||||||
|
}
|
||||||
|
}
|
48
test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java
Normal file
48
test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests for connection related timeouts
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutNoProxySync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutNoProxySync extends AbstractConnectTimeout {
|
||||||
|
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutNoProxySync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
super.timeoutNoProxySync(requestVersion, scheme, method, connectTimeout, requestTimeout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests for connection related timeouts
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutWithProxyAsync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutWithProxyAsync extends AbstractConnectTimeout {
|
||||||
|
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutWithProxyAsync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
{
|
||||||
|
super.timeoutWithProxyAsync(requestVersion, scheme, method, connectTimeout, requestTimeout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.http.HttpClient.Version;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Tests for connection related timeouts
|
||||||
|
* @bug 8208391
|
||||||
|
* @run testng/othervm ConnectTimeoutWithProxySync
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ConnectTimeoutWithProxySync extends AbstractConnectTimeout {
|
||||||
|
|
||||||
|
@Test(dataProvider = "variants")
|
||||||
|
@Override
|
||||||
|
public void timeoutWithProxySync(Version requestVersion,
|
||||||
|
String scheme,
|
||||||
|
String method,
|
||||||
|
Duration connectTimeout,
|
||||||
|
Duration requestTimeout)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
super.timeoutWithProxySync(requestVersion, scheme, method, connectTimeout, requestTimeout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ import java.net.http.HttpClient.Redirect;
|
||||||
import java.net.http.HttpClient.Version;
|
import java.net.http.HttpClient.Version;
|
||||||
import jdk.testlibrary.SimpleSSLContext;
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
import static java.time.Duration.*;
|
||||||
import static org.testng.Assert.*;
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -74,6 +75,7 @@ public class HttpClientBuilderTest {
|
||||||
// Empty optionals and defaults
|
// Empty optionals and defaults
|
||||||
assertFalse(client.authenticator().isPresent());
|
assertFalse(client.authenticator().isPresent());
|
||||||
assertFalse(client.cookieHandler().isPresent());
|
assertFalse(client.cookieHandler().isPresent());
|
||||||
|
assertFalse(client.connectTimeout().isPresent());
|
||||||
assertFalse(client.executor().isPresent());
|
assertFalse(client.executor().isPresent());
|
||||||
assertFalse(client.proxy().isPresent());
|
assertFalse(client.proxy().isPresent());
|
||||||
assertTrue(client.sslParameters() != null);
|
assertTrue(client.sslParameters() != null);
|
||||||
|
@ -88,6 +90,7 @@ public class HttpClientBuilderTest {
|
||||||
HttpClient.Builder builder = HttpClient.newBuilder();
|
HttpClient.Builder builder = HttpClient.newBuilder();
|
||||||
assertThrows(NPE, () -> builder.authenticator(null));
|
assertThrows(NPE, () -> builder.authenticator(null));
|
||||||
assertThrows(NPE, () -> builder.cookieHandler(null));
|
assertThrows(NPE, () -> builder.cookieHandler(null));
|
||||||
|
assertThrows(NPE, () -> builder.connectTimeout(null));
|
||||||
assertThrows(NPE, () -> builder.executor(null));
|
assertThrows(NPE, () -> builder.executor(null));
|
||||||
assertThrows(NPE, () -> builder.proxy(null));
|
assertThrows(NPE, () -> builder.proxy(null));
|
||||||
assertThrows(NPE, () -> builder.sslParameters(null));
|
assertThrows(NPE, () -> builder.sslParameters(null));
|
||||||
|
@ -128,6 +131,26 @@ public class HttpClientBuilderTest {
|
||||||
assertTrue(builder.build().cookieHandler().get() == c);
|
assertTrue(builder.build().cookieHandler().get() == c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectTimeout() {
|
||||||
|
HttpClient.Builder builder = HttpClient.newBuilder();
|
||||||
|
Duration a = Duration.ofSeconds(5);
|
||||||
|
builder.connectTimeout(a);
|
||||||
|
assertTrue(builder.build().connectTimeout().get() == a);
|
||||||
|
Duration b = Duration.ofMinutes(1);
|
||||||
|
builder.connectTimeout(b);
|
||||||
|
assertTrue(builder.build().connectTimeout().get() == b);
|
||||||
|
assertThrows(NPE, () -> builder.cookieHandler(null));
|
||||||
|
Duration c = Duration.ofHours(100);
|
||||||
|
builder.connectTimeout(c);
|
||||||
|
assertTrue(builder.build().connectTimeout().get() == c);
|
||||||
|
|
||||||
|
assertThrows(IAE, () -> builder.connectTimeout(ZERO));
|
||||||
|
assertThrows(IAE, () -> builder.connectTimeout(ofSeconds(0)));
|
||||||
|
assertThrows(IAE, () -> builder.connectTimeout(ofSeconds(-1)));
|
||||||
|
assertThrows(IAE, () -> builder.connectTimeout(ofNanos(-100)));
|
||||||
|
}
|
||||||
|
|
||||||
static class TestExecutor implements Executor {
|
static class TestExecutor implements Executor {
|
||||||
public void execute(Runnable r) { }
|
public void execute(Runnable r) { }
|
||||||
}
|
}
|
||||||
|
@ -292,6 +315,7 @@ public class HttpClientBuilderTest {
|
||||||
|
|
||||||
static class MockHttpClient extends HttpClient {
|
static class MockHttpClient extends HttpClient {
|
||||||
@Override public Optional<CookieHandler> cookieHandler() { return null; }
|
@Override public Optional<CookieHandler> cookieHandler() { return null; }
|
||||||
|
@Override public Optional<Duration> connectTimeout() { return null; }
|
||||||
@Override public Redirect followRedirects() { return null; }
|
@Override public Redirect followRedirects() { return null; }
|
||||||
@Override public Optional<ProxySelector> proxy() { return null; }
|
@Override public Optional<ProxySelector> proxy() { return null; }
|
||||||
@Override public SSLContext sslContext() { return null; }
|
@Override public SSLContext sslContext() { return null; }
|
||||||
|
|
|
@ -164,6 +164,7 @@ public class TimeoutBasic {
|
||||||
throw new RuntimeException("Unexpected response: " + resp.statusCode());
|
throw new RuntimeException("Unexpected response: " + resp.statusCode());
|
||||||
} catch (CompletionException e) {
|
} catch (CompletionException e) {
|
||||||
if (!(e.getCause() instanceof HttpTimeoutException)) {
|
if (!(e.getCause() instanceof HttpTimeoutException)) {
|
||||||
|
e.printStackTrace(out);
|
||||||
throw new RuntimeException("Unexpected exception: " + e.getCause());
|
throw new RuntimeException("Unexpected exception: " + e.getCause());
|
||||||
} else {
|
} else {
|
||||||
out.println("Caught expected timeout: " + e.getCause());
|
out.println("Caught expected timeout: " + e.getCause());
|
||||||
|
|
|
@ -64,6 +64,7 @@ public class JavadocExamples {
|
||||||
HttpClient client = HttpClient.newBuilder()
|
HttpClient client = HttpClient.newBuilder()
|
||||||
.version(Version.HTTP_1_1)
|
.version(Version.HTTP_1_1)
|
||||||
.followRedirects(Redirect.NORMAL)
|
.followRedirects(Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
|
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
|
||||||
.authenticator(Authenticator.getDefault())
|
.authenticator(Authenticator.getDefault())
|
||||||
.build();
|
.build();
|
||||||
|
@ -74,7 +75,7 @@ public class JavadocExamples {
|
||||||
//Asynchronous Example
|
//Asynchronous Example
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("https://foo.com/"))
|
.uri(URI.create("https://foo.com/"))
|
||||||
.timeout(Duration.ofMinutes(1))
|
.timeout(Duration.ofMinutes(2))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
|
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -50,6 +51,11 @@ public class DelegatingHttpClient extends HttpClient {
|
||||||
return client.cookieHandler();
|
return client.cookieHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Duration> connectTimeout() {
|
||||||
|
return client.connectTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Redirect followRedirects() {
|
public Redirect followRedirects() {
|
||||||
return client.followRedirects();
|
return client.followRedirects();
|
||||||
|
|
|
@ -31,6 +31,7 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -284,7 +285,8 @@ public class ConnectionPoolTest {
|
||||||
|
|
||||||
// All these throw errors
|
// All these throw errors
|
||||||
@Override public HttpPublisher publisher() {return error();}
|
@Override public HttpPublisher publisher() {return error();}
|
||||||
@Override public CompletableFuture<Void> connectAsync() {return error();}
|
@Override public CompletableFuture<Void> connectAsync(Exchange<?> e) {return error();}
|
||||||
|
@Override public CompletableFuture<Void> finishConnect() {return error();}
|
||||||
@Override SocketChannel channel() {return error();}
|
@Override SocketChannel channel() {return error();}
|
||||||
@Override
|
@Override
|
||||||
FlowTube getConnectionFlow() {return flow;}
|
FlowTube getConnectionFlow() {return flow;}
|
||||||
|
@ -296,6 +298,7 @@ public class ConnectionPoolTest {
|
||||||
}
|
}
|
||||||
final ConnectionPool pool;
|
final ConnectionPool pool;
|
||||||
@Override public Optional<CookieHandler> cookieHandler() {return error();}
|
@Override public Optional<CookieHandler> cookieHandler() {return error();}
|
||||||
|
@Override public Optional<Duration> connectTimeout() {return error();}
|
||||||
@Override public HttpClient.Redirect followRedirects() {return error();}
|
@Override public HttpClient.Redirect followRedirects() {return error();}
|
||||||
@Override public Optional<ProxySelector> proxy() {return error();}
|
@Override public Optional<ProxySelector> proxy() {return error();}
|
||||||
@Override public SSLContext sslContext() {return error();}
|
@Override public SSLContext sslContext() {return error();}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue