mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-25 09:40:10 +00:00
8368528: HttpClient.Builder.connectTimeout should accept arbitrarily large values
Reviewed-by: dfuchs
This commit is contained in:
parent
3f40f4c362
commit
c754e3e095
@ -499,22 +499,28 @@ abstract class HttpQuicConnection extends HttpConnection {
|
||||
}, exchange.parentExecutor.safeDelegate());
|
||||
}
|
||||
|
||||
Optional<Duration> timeout = client().connectTimeout();
|
||||
CompletableFuture<CompletableFuture<Void>> fxi = handshakeCfCf;
|
||||
|
||||
// In case of connection timeout, set up a timeout on the handshakeCfCf.
|
||||
// Note: this is a different timeout than the direct connection timeout.
|
||||
if (timeout.isPresent()) {
|
||||
Duration timeout = client().connectTimeout().orElse(null);
|
||||
if (timeout != null) {
|
||||
// In case of timeout we need to close the quic connection
|
||||
debug.log("setting up quic connect timeout: " + timeout.get().toMillis());
|
||||
debug.log("setting up quic connect timeout: " + timeout);
|
||||
long timeoutMillis;
|
||||
try {
|
||||
timeoutMillis = timeout.toMillis();
|
||||
} catch (ArithmeticException _) {
|
||||
timeoutMillis = Long.MAX_VALUE;
|
||||
}
|
||||
fxi = handshakeCfCf.completeOnTimeout(
|
||||
MinimalFuture.failedFuture(new HttpConnectTimeoutException("quic connect timeout")),
|
||||
timeout.get().toMillis(), TimeUnit.MILLISECONDS);
|
||||
timeoutMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// If we have set up any timeout, arrange to close the quicConnection
|
||||
// if one of the timeout expires
|
||||
if (timeout.isPresent() || directTimeout.isPresent()) {
|
||||
if (timeout != null || directTimeout.isPresent()) {
|
||||
fxi = fxi.handleAsync(this::handleTimeout, exchange.parentExecutor.safeDelegate());
|
||||
}
|
||||
return fxi.thenCompose(Function.identity());
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,14 +28,22 @@ import java.time.DateTimeException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.time.temporal.UnsupportedTemporalTypeException;
|
||||
|
||||
/**
|
||||
* A Deadline represents an instant on a {@linkplain TimeLine time line}.
|
||||
* An instantaneous point on the {@linkplain TimeLine time-line}.
|
||||
* <p>
|
||||
* This class is immutable and thread-safe.
|
||||
* <p id="overflow">
|
||||
* Operations that add or subtract durations to a {@code Deadline}, whether
|
||||
* represented as a {@link Duration} or as a {@code long} time increment (such
|
||||
* as seconds or nanoseconds) do not throw on numeric overflow if the resulting
|
||||
* {@code Deadline} would exceed {@link #MAX} or be less than {@link #MIN}.
|
||||
* Instead, {@code MAX} or {@code MIN} is returned, respectively. Similarly,
|
||||
* methods that return a duration as a {@code long} will either return
|
||||
* {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE} if the returned quantity
|
||||
* would exceed the capacity of a {@code long}.
|
||||
*/
|
||||
public final class Deadline implements Comparable<Deadline> {
|
||||
|
||||
@ -49,17 +57,24 @@ public final class Deadline implements Comparable<Deadline> {
|
||||
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified duration in nanoseconds added.
|
||||
* {@return a deadline with the specified duration in nanoseconds added}
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MAX} if the provided duration is positive,
|
||||
* {@link Deadline#MIN} otherwise.
|
||||
*
|
||||
* @param nanosToAdd the nanoseconds to add, positive or negative
|
||||
* @return a {@code Deadline} based on this deadline with the specified nanoseconds added, not null
|
||||
* @throws DateTimeException if the result exceeds the maximum or minimum deadline
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*/
|
||||
public Deadline plusNanos(long nanosToAdd) {
|
||||
return new Deadline(deadline.plusNanos(nanosToAdd));
|
||||
if (nanosToAdd == 0) return this;
|
||||
try {
|
||||
return new Deadline(deadline.plusNanos(nanosToAdd));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return nanosToAdd > 0 ? Deadline.MAX : Deadline.MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,92 +104,116 @@ public final class Deadline implements Comparable<Deadline> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified amount subtracted.
|
||||
* <p>
|
||||
* This returns a {@code Deadline}, based on this one, with the specified amount subtracted.
|
||||
* The amount is typically {@link Duration} but may be any other type implementing
|
||||
* the {@link TemporalAmount} interface.
|
||||
* {@return a deadline with the specified amount subtracted from this deadline}
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MIN} if the provided duration is positive,
|
||||
* {@link Deadline#MAX} otherwise.
|
||||
*
|
||||
* @param amountToSubtract the amount to subtract, not null
|
||||
* @return a {@code Deadline} based on this deadline with the subtraction made, not null
|
||||
* @throws DateTimeException if the subtraction cannot be made
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
* @param duration the amount to subtract, not null
|
||||
*/
|
||||
public Deadline minus(TemporalAmount amountToSubtract) {
|
||||
return Deadline.of(deadline.minus(amountToSubtract));
|
||||
public Deadline minus(Duration duration) {
|
||||
if (duration.isZero()) return this;
|
||||
try {
|
||||
return Deadline.of(deadline.minus(duration));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return duration.isPositive() ? Deadline.MIN : Deadline.MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified amount added.
|
||||
* {@return a deadline with the specified amount added to this deadline}
|
||||
* <p>
|
||||
* This returns a {@code Deadline}, based on this one, with the amount
|
||||
* in terms of the unit added. If it is not possible to add the amount, because the
|
||||
* unit is not supported or for some other reason, an exception is thrown.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MAX} if the provided amount is positive,
|
||||
* {@link Deadline#MIN} otherwise.
|
||||
*
|
||||
* @see Instant#plus(long, TemporalUnit)
|
||||
*
|
||||
* @param amountToAdd the amount of the unit to add to the result, may be negative
|
||||
* @param unit the unit of the amount to add, not null
|
||||
* @return a {@code Deadline} based on this deadline with the specified amount added, not null
|
||||
* @throws DateTimeException if the addition cannot be made
|
||||
* @throws UnsupportedTemporalTypeException if the unit is not supported
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*/
|
||||
public Deadline plus(long amountToAdd, TemporalUnit unit) {
|
||||
if (amountToAdd == 0) return this;
|
||||
return Deadline.of(deadline.plus(amountToAdd, unit));
|
||||
try {
|
||||
return Deadline.of(deadline.plus(amountToAdd, unit));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return amountToAdd > 0 ? Deadline.MAX : Deadline.MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified duration in seconds added.
|
||||
* {@return a deadline with the specified duration in seconds added to this deadline}
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MAX} if the provided duration is positive,
|
||||
* {@link Deadline#MIN} otherwise.
|
||||
*
|
||||
* @param secondsToAdd the seconds to add, positive or negative
|
||||
* @return a {@code Deadline} based on this deadline with the specified seconds added, not null
|
||||
* @throws DateTimeException if the result exceeds the maximum or minimum deadline
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*/
|
||||
public Deadline plusSeconds(long secondsToAdd) {
|
||||
if (secondsToAdd == 0) return this;
|
||||
return Deadline.of(deadline.plusSeconds(secondsToAdd));
|
||||
try {
|
||||
return Deadline.of(deadline.plusSeconds(secondsToAdd));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return secondsToAdd > 0 ? Deadline.MAX : Deadline.MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified duration in milliseconds added.
|
||||
* {@return a deadline with the specified duration in milliseconds added to this deadline}
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MAX} if the provided duration is positive,
|
||||
* {@link Deadline#MIN} otherwise.
|
||||
*
|
||||
* @param millisToAdd the milliseconds to add, positive or negative
|
||||
* @return a {@code Deadline} based on this deadline with the specified milliseconds added, not null
|
||||
* @throws DateTimeException if the result exceeds the maximum or minimum deadline
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*/
|
||||
public Deadline plusMillis(long millisToAdd) {
|
||||
if (millisToAdd == 0) return this;
|
||||
return Deadline.of(deadline.plusMillis(millisToAdd));
|
||||
try {
|
||||
return Deadline.of(deadline.plusMillis(millisToAdd));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return millisToAdd > 0 ? Deadline.MAX : Deadline.MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this deadline with the specified amount added.
|
||||
* <p>
|
||||
* This returns a {@code Deadline}, based on this one, with the specified amount added.
|
||||
* The amount is typically {@link Duration} but may be any other type implementing
|
||||
* the {@link TemporalAmount} interface.
|
||||
* {@return a deadline with the specified duration added to this deadline}
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Deadline#MAX} if the provided duration is positive,
|
||||
* {@link Deadline#MIN} otherwise.
|
||||
*
|
||||
* @param amountToAdd the amount to add, not null
|
||||
* @return a {@code Deadline} based on this deadline with the addition made, not null
|
||||
* @throws DateTimeException if the addition cannot be made
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
* @param duration the duration to add, not null
|
||||
*/
|
||||
public Deadline plus(TemporalAmount amountToAdd) {
|
||||
return Deadline.of(deadline.plus(amountToAdd));
|
||||
public Deadline plus(Duration duration) {
|
||||
if (duration.isZero()) return this;
|
||||
try {
|
||||
return Deadline.of(deadline.plus(duration));
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
return duration.isPositive() ? Deadline.MAX : Deadline.MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,16 +227,24 @@ public final class Deadline implements Comparable<Deadline> {
|
||||
* complete units between the two deadlines.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
* <p>
|
||||
* On {@linkplain ##overflow numeric overflows}, this method will return
|
||||
* {@link Long#MAX_VALUE} if the current deadline is before the provided end
|
||||
* deadline, {@link Long#MIN_VALUE} otherwise.
|
||||
*
|
||||
* @param endExclusive the end deadline, exclusive
|
||||
* @param unit the unit to measure the amount in, not null
|
||||
* @return the amount of time between this deadline and the end deadline
|
||||
* @throws DateTimeException if the amount cannot be calculated
|
||||
* @throws UnsupportedTemporalTypeException if the unit is not supported
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*/
|
||||
public long until(Deadline endExclusive, TemporalUnit unit) {
|
||||
return deadline.until(endExclusive.deadline, unit);
|
||||
try {
|
||||
return deadline.until(endExclusive.deadline, unit);
|
||||
} catch (DateTimeException | // "Instant exceeds minimum or maximum instant"
|
||||
ArithmeticException _) { // "long overflow"
|
||||
int delta = compareTo(endExclusive);
|
||||
return delta < 0 ? Long.MAX_VALUE : Long.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,10 +313,13 @@ public final class Deadline implements Comparable<Deadline> {
|
||||
* @param startInclusive the start deadline, inclusive, not null
|
||||
* @param endExclusive the end deadline, exclusive, not null
|
||||
* @return a {@code Duration}, not null
|
||||
* @throws DateTimeException if the seconds between the deadline cannot be obtained
|
||||
* @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration}
|
||||
*/
|
||||
public static Duration between(Deadline startInclusive, Deadline endExclusive) {
|
||||
if (startInclusive.equals(endExclusive)) return Duration.ZERO;
|
||||
// `Deadline` works with `Instant` under the hood.
|
||||
// Delta between `Instant.MIN` and `Instant.MAX` fits in a `Duration`.
|
||||
// Hence, we should never receive a numeric overflow while calculating the delta between two deadlines.
|
||||
return Duration.between(startInclusive.deadline, endExclusive.deadline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
349
test/jdk/java/net/httpclient/DurationOverflowTest.java
Normal file
349
test/jdk/java/net/httpclient/DurationOverflowTest.java
Normal file
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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 jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestEchoHandler;
|
||||
import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestServer;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import jdk.test.lib.net.SimpleSSLContext;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Version;
|
||||
import java.net.http.HttpOption;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||
import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderFor;
|
||||
|
||||
/*
|
||||
* @test id=withoutPropertyConfig
|
||||
* @bug 8368528
|
||||
* @summary Verifies that `Duration`-accepting programmatic public APIs, either
|
||||
* individually, or in combination, work with arbitrarily large values
|
||||
*
|
||||
* @library /test/jdk/java/net/httpclient/lib
|
||||
* /test/lib
|
||||
*
|
||||
* @run junit DurationOverflowTest
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=withPropertyConfig
|
||||
* @bug 8368528
|
||||
* @summary Verifies that `Duration`-accepting programmatic public APIs, either
|
||||
* individually, or in combination, work with arbitrarily large values
|
||||
* when combined with duration-accepting property-based public APIs.
|
||||
*
|
||||
* @library /test/jdk/java/net/httpclient/lib
|
||||
* /test/lib
|
||||
*
|
||||
* @comment 9223372036854775807 is the value of `Long.MAX_VALUE`
|
||||
*
|
||||
* @run junit/othervm
|
||||
* -Djdk.httpclient.keepalive.timeout=9223372036854775807
|
||||
* DurationOverflowTest
|
||||
*
|
||||
* @comment `h3` infra is also enabled for this test since `j.h.k.timeout.h3`
|
||||
* defaults to `j.h.k.timeout.h2`
|
||||
* @run junit/othervm
|
||||
* -Djdk.httpclient.keepalive.timeout.h2=9223372036854775807
|
||||
* -DallowedInfras=h2,h2s,h3
|
||||
* DurationOverflowTest
|
||||
*
|
||||
* @run junit/othervm
|
||||
* -Djdk.httpclient.keepalive.timeout.h3=9223372036854775807
|
||||
* -DallowedInfras=h3
|
||||
* DurationOverflowTest
|
||||
*/
|
||||
|
||||
public class DurationOverflowTest {
|
||||
|
||||
private static final String CLASS_NAME = DurationOverflowTest.class.getSimpleName();
|
||||
|
||||
private static final Logger LOGGER = Utils.getDebugLogger(CLASS_NAME::toString, Utils.DEBUG);
|
||||
|
||||
private static final SSLContext SSL_CONTEXT = createSslContext();
|
||||
|
||||
private static SSLContext createSslContext() {
|
||||
try {
|
||||
return new SimpleSSLContext().get();
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<Infra> INFRAS = loadInfras();
|
||||
|
||||
private static final class Infra implements AutoCloseable {
|
||||
|
||||
private static final AtomicInteger SERVER_COUNTER = new AtomicInteger();
|
||||
|
||||
private final String serverId;
|
||||
|
||||
private final HttpTestServer server;
|
||||
|
||||
private final Supplier<HttpClient.Builder> clientBuilderSupplier;
|
||||
|
||||
private final Supplier<HttpRequest.Builder> requestBuilderSupplier;
|
||||
|
||||
private final boolean secure;
|
||||
|
||||
private Infra(
|
||||
String serverId,
|
||||
HttpTestServer server,
|
||||
Supplier<HttpClient.Builder> clientBuilderSupplier,
|
||||
Supplier<HttpRequest.Builder> requestBuilderSupplier,
|
||||
boolean secure) {
|
||||
this.serverId = serverId;
|
||||
this.server = server;
|
||||
this.clientBuilderSupplier = clientBuilderSupplier;
|
||||
this.requestBuilderSupplier = requestBuilderSupplier;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
private static Infra of(Version version, boolean secure) {
|
||||
|
||||
// Create the server and the request URI
|
||||
var sslContext = secure ? SSL_CONTEXT : null;
|
||||
var server = createServer(version, sslContext);
|
||||
server.getVersion();
|
||||
var handlerPath = "/%s/".formatted(CLASS_NAME);
|
||||
var requestUriScheme = secure ? "https" : "http";
|
||||
var requestUri = URI.create("%s://%s%s-".formatted(requestUriScheme, server.serverAuthority(), handlerPath));
|
||||
|
||||
// Register the request handler
|
||||
var serverId = "" + SERVER_COUNTER.getAndIncrement();
|
||||
server.addHandler(
|
||||
// Intentionally opting for receiving a body to cover code paths associated with its retrieval
|
||||
new HttpTestEchoHandler(false),
|
||||
handlerPath);
|
||||
|
||||
// Create client & request builders
|
||||
Supplier<HttpClient.Builder> clientBuilderSupplier =
|
||||
() -> createClientBuilderFor(version)
|
||||
.version(version)
|
||||
.sslContext(SSL_CONTEXT)
|
||||
.proxy(NO_PROXY);
|
||||
Supplier<HttpRequest.Builder> requestBuilderSupplier =
|
||||
() -> createRequestBuilder(requestUri, version);
|
||||
|
||||
// Create the pair
|
||||
var pair = new Infra(serverId, server, clientBuilderSupplier, requestBuilderSupplier, secure);
|
||||
pair.server.start();
|
||||
LOGGER.log("Server[%s] is started at `%s`", serverId, server.serverAuthority());
|
||||
return pair;
|
||||
|
||||
}
|
||||
|
||||
private static HttpTestServer createServer(Version version, SSLContext sslContext) {
|
||||
try {
|
||||
return switch (version) {
|
||||
case HTTP_1_1, HTTP_2 -> HttpTestServer.create(version, sslContext, null);
|
||||
case HTTP_3 -> HttpTestServer.create(HTTP_3_URI_ONLY, sslContext, null);
|
||||
};
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpRequest.Builder createRequestBuilder(URI uri, Version version) {
|
||||
var requestBuilder = HttpRequest.newBuilder(uri).version(version).HEAD();
|
||||
if (Version.HTTP_3.equals(version)) {
|
||||
requestBuilder.setOption(HttpOption.H3_DISCOVERY, HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY);
|
||||
}
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LOGGER.log("Server[%s] is stopping", serverId);
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var version = server.getVersion();
|
||||
var versionString = version.toString();
|
||||
return switch (version) {
|
||||
case HTTP_1_1, HTTP_2 -> secure ? versionString.replaceFirst("_", "S_") : versionString;
|
||||
case HTTP_3 -> versionString;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<Infra> loadInfras() {
|
||||
return Stream
|
||||
.of(System.getProperty("allowedInfras", "h1,h1s,h2,h2s,h3").split(","))
|
||||
.map(infra -> {
|
||||
LOGGER.log("Loading test infrastructure: `%s`", infra);
|
||||
return switch (infra) {
|
||||
case "h1" -> Infra.of(Version.HTTP_1_1, false);
|
||||
case "h1s" -> Infra.of(Version.HTTP_1_1, true);
|
||||
case "h2" -> Infra.of(Version.HTTP_2, false);
|
||||
case "h2s" -> Infra.of(Version.HTTP_2, true);
|
||||
case "h3" -> Infra.of(Version.HTTP_3, true);
|
||||
default -> throw new IllegalArgumentException("Unknown test infrastructure: " + infra);
|
||||
};
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownInfras() {
|
||||
LOGGER.log("Tearing down test infrastructure");
|
||||
Exception[] exceptionRef = {null};
|
||||
infras().forEach(infra -> {
|
||||
try {
|
||||
infra.close();
|
||||
} catch (Exception exception) {
|
||||
if (exceptionRef[0] == null) {
|
||||
exceptionRef[0] = exception;
|
||||
} else {
|
||||
exceptionRef[0].addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (exceptionRef[0] != null) {
|
||||
throw new RuntimeException("Failed tearing down one or more test infrastructures", exceptionRef[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Infra> infras() {
|
||||
return INFRAS.stream();
|
||||
}
|
||||
|
||||
public static final Set<Duration> EXCESSIVE_DURATIONS = Set.of(
|
||||
Duration.MAX,
|
||||
// This triggers different exceptions than the ones triggered by `Duration.MAX`
|
||||
Duration.ofMillis(Long.MAX_VALUE));
|
||||
|
||||
private static Stream<InfraDurationPair> infraDurationPairs() {
|
||||
return infras().flatMap(infra -> EXCESSIVE_DURATIONS.stream()
|
||||
.map(duration -> new InfraDurationPair(infra, duration)));
|
||||
}
|
||||
|
||||
private record InfraDurationPair(Infra infra, Duration duration) {}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("infraDurationPairs")
|
||||
void testClientConnectTimeout(InfraDurationPair pair) throws Exception {
|
||||
testConfig(pair.infra, clientBuilder -> clientBuilder.connectTimeout(pair.duration), null);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("infraDurationPairs")
|
||||
void testRequestTimeout(InfraDurationPair pair) throws Exception {
|
||||
testConfig(pair.infra, null, requestBuilder -> requestBuilder.timeout(pair.duration));
|
||||
}
|
||||
|
||||
private static Stream<InfraDurationDurationTriple> infraDurationDurationTriples() {
|
||||
return infras().flatMap(infra -> EXCESSIVE_DURATIONS.stream()
|
||||
.flatMap(duration1 -> EXCESSIVE_DURATIONS.stream()
|
||||
.map(duration2 -> new InfraDurationDurationTriple(infra, duration1, duration2))));
|
||||
}
|
||||
|
||||
private record InfraDurationDurationTriple(Infra infra, Duration duration1, Duration duration2) {}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("infraDurationDurationTriples")
|
||||
void testClientConnectTimeoutAndRequestTimeout(InfraDurationDurationTriple triple) throws Exception {
|
||||
testConfig(
|
||||
triple.infra,
|
||||
clientBuilder -> clientBuilder.connectTimeout(triple.duration1),
|
||||
requestBuilder -> requestBuilder.timeout(triple.duration2));
|
||||
}
|
||||
|
||||
private static void testConfig(
|
||||
Infra infra,
|
||||
Consumer<HttpClient.Builder> clientBuilderConsumer,
|
||||
Consumer<HttpRequest.Builder> requestBuilderConsumer)
|
||||
throws Exception {
|
||||
|
||||
// Create the client
|
||||
var clientBuilder = infra.clientBuilderSupplier.get();
|
||||
if (clientBuilderConsumer != null) {
|
||||
clientBuilderConsumer.accept(clientBuilder);
|
||||
}
|
||||
try (var client = clientBuilder.build()) {
|
||||
|
||||
// Create the request
|
||||
byte[] expectedBytes = "abc".repeat(8192).getBytes(US_ASCII);
|
||||
var requestBuilder = infra.requestBuilderSupplier.get()
|
||||
// Intentionally opting for sending a body to cover code paths associated with its delivery
|
||||
.POST(BodyPublishers.ofByteArray(expectedBytes));
|
||||
if (requestBuilderConsumer != null) {
|
||||
requestBuilderConsumer.accept(requestBuilder);
|
||||
}
|
||||
var request = requestBuilder.build();
|
||||
|
||||
// Execute the request.
|
||||
// Doing it twice to touch code paths before & after a protocol upgrade, if present.
|
||||
for (int requestIndex = 0; requestIndex < 2; requestIndex++) {
|
||||
LOGGER.log("Executing request (attempt=%s)", requestIndex + 1);
|
||||
var response = client.send(request, BodyHandlers.ofByteArray());
|
||||
|
||||
// Verify the response status code
|
||||
if (response.statusCode() != 200) {
|
||||
var message = String.format(
|
||||
"Unexpected status code: %s (attempt=%s)",
|
||||
response.statusCode(), requestIndex + 1);
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
|
||||
// Verify the response body
|
||||
int mismatchIndex = Arrays.mismatch(expectedBytes, response.body());
|
||||
if (mismatchIndex > 0) {
|
||||
var message = String.format(
|
||||
"Body mismatch at index %s (attempt=%s)",
|
||||
mismatchIndex, requestIndex + 1);
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8368528
|
||||
* @summary Verifies that `Deadline` returns extremums on numeric overflows
|
||||
* @modules java.net.http/jdk.internal.net.http.common:+open
|
||||
* @run junit java.net.http/jdk.internal.net.http.common.DeadlineOverflowTest
|
||||
*/
|
||||
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.common;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.NANOS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DeadlineOverflowTest {
|
||||
|
||||
@Test
|
||||
void test_DeadlineOf_InstantMin() {
|
||||
assertEquals(Instant.MIN, Deadline.of(Instant.MIN).asInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_DeadlineOf_InstantMax() {
|
||||
assertEquals(Instant.MAX, Deadline.of(Instant.MAX).asInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusNanos_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.plusNanos(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusNanos_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.plusNanos(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_minus_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.minus(Duration.ofNanos(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_minus_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.minus(Duration.ofNanos(-1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusAmount_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.plus(-1, ChronoUnit.NANOS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusAmount_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.plus(1, ChronoUnit.NANOS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusSeconds_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.plusSeconds(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusSeconds_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.plusSeconds(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusMillis_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.plusMillis(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusMillis_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.plusMillis(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusDuration_min() {
|
||||
assertEquals(Deadline.MIN, Deadline.MIN.plus(Duration.ofNanos(-1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_plusDuration_max() {
|
||||
assertEquals(Deadline.MAX, Deadline.MAX.plus(Duration.ofNanos(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_until_min() {
|
||||
assertEquals(Long.MIN_VALUE, Deadline.MAX.until(Deadline.MIN, NANOS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_until_max() {
|
||||
assertEquals(Long.MAX_VALUE, Deadline.MIN.until(Deadline.MAX, NANOS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_between_min() {
|
||||
Duration delta = Duration.between(Instant.MAX, Instant.MIN);
|
||||
assertEquals(delta, Deadline.between(Deadline.MAX, Deadline.MIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_between_max() {
|
||||
Duration delta = Duration.between(Instant.MIN, Instant.MAX);
|
||||
assertEquals(delta, Deadline.between(Deadline.MIN, Deadline.MAX));
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user