8374706: HttpClient: Ensure that request body publishers support multiple subscriptions

Reviewed-by: dfuchs, jpai
This commit is contained in:
Volkan Yazici 2026-03-23 14:58:47 +00:00
parent 00ee63e99e
commit 2a64074a8b
10 changed files with 340 additions and 91 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -61,7 +61,7 @@ public final class ByteBufferUtils {
private static byte[] bytes(ByteBuffer buffer) {
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
buffer.get(buffer.position(), bytes);
return bytes;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -25,10 +25,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static java.net.http.HttpRequest.BodyPublishers.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -37,16 +40,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* @test
* @bug 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::fromPublisher` behavior
* @build RecordingSubscriber
* @run junit FromPublisherTest
* @build ByteBufferUtils
* RecordingSubscriber
* ReplayTestSupport
* @run junit ${test.main.class}
*/
class FromPublisherTest {
class FromPublisherTest extends ReplayTestSupport {
@Test
void testNullPublisher() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.fromPublisher(null));
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.fromPublisher(null, 1));
assertThrows(NullPointerException.class, () -> fromPublisher(null));
assertThrows(NullPointerException.class, () -> fromPublisher(null, 1));
}
@ParameterizedTest
@ -54,7 +59,7 @@ class FromPublisherTest {
void testInvalidContentLength(long contentLength) {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> HttpRequest.BodyPublishers.fromPublisher(null, contentLength));
() -> fromPublisher(null, contentLength));
String exceptionMessage = exception.getMessage();
assertTrue(
exceptionMessage.contains("non-positive contentLength"),
@ -64,29 +69,26 @@ class FromPublisherTest {
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4})
void testValidContentLength(long contentLength) {
HttpRequest.BodyPublisher publisher =
HttpRequest.BodyPublishers.fromPublisher(HttpRequest.BodyPublishers.noBody(), contentLength);
BodyPublisher publisher = fromPublisher(noBody(), contentLength);
assertEquals(contentLength, publisher.contentLength());
}
@Test
void testNoContentLength() {
HttpRequest.BodyPublisher publisher =
HttpRequest.BodyPublishers.fromPublisher(HttpRequest.BodyPublishers.noBody());
BodyPublisher publisher = fromPublisher(noBody());
assertEquals(-1, publisher.contentLength());
}
@Test
void testNullSubscriber() {
HttpRequest.BodyPublisher publisher =
HttpRequest.BodyPublishers.fromPublisher(HttpRequest.BodyPublishers.noBody());
BodyPublisher publisher = fromPublisher(noBody());
assertThrows(NullPointerException.class, () -> publisher.subscribe(null));
}
@Test
void testDelegation() throws InterruptedException {
BlockingQueue<Object> publisherInvocations = new LinkedBlockingQueue<>();
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.fromPublisher(subscriber -> {
BodyPublisher publisher = fromPublisher(subscriber -> {
publisherInvocations.add("subscribe");
publisherInvocations.add(subscriber);
});
@ -97,4 +99,13 @@ class FromPublisherTest {
assertTrue(subscriber.invocations.isEmpty());
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
byte[] content = ByteBufferUtils.byteArrayOfLength(10);
ByteBuffer expectedBuffer = ByteBuffer.wrap(content);
BodyPublisher delegatePublisher = ofByteArray(content);
BodyPublisher publisher = fromPublisher(delegatePublisher, delegatePublisher.contentLength());
return List.of(new ReplayTarget(expectedBuffer, publisher));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -23,7 +23,10 @@
import org.junit.jupiter.api.Test;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.Flow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -32,17 +35,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @test
* @bug 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::noBody` behavior
* @build RecordingSubscriber
* @run junit NoBodyTest
* @build ByteBufferUtils
* RecordingSubscriber
* ReplayTestSupport
* @run junit ${test.main.class}
*/
class NoBodyTest {
class NoBodyTest extends ReplayTestSupport {
@Test
void test() throws InterruptedException {
// Create the publisher
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.noBody();
BodyPublisher publisher = BodyPublishers.noBody();
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -54,4 +59,11 @@ class NoBodyTest {
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
ByteBuffer expectedBuffer = ByteBuffer.wrap(new byte[0]);
BodyPublisher publisher = BodyPublishers.noBody();
return List.of(new ReplayTarget(expectedBuffer, publisher));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -25,7 +25,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@ -39,30 +40,34 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* @test
* @bug 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::ofByteArray` behavior
* @build RecordingSubscriber
* @run junit OfByteArrayTest
*
* @build ByteBufferUtils
* RecordingSubscriber
* ReplayTestSupport
*
* @run junit ${test.main.class}
*
* @comment Using `main/othervm` to initiate tests that depend on a custom-configured JVM
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking "" 0 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking a 0 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking a 1 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking a 0 1 a
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking ab 0 1 a
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking ab 1 1 b
* @run main/othervm -Djdk.httpclient.bufsize=3 OfByteArrayTest testChunking ab 0 2 ab
* @run main/othervm -Djdk.httpclient.bufsize=1 OfByteArrayTest testChunking abc 0 3 a:b:c
* @run main/othervm -Djdk.httpclient.bufsize=2 OfByteArrayTest testChunking abc 0 3 ab:c
* @run main/othervm -Djdk.httpclient.bufsize=2 OfByteArrayTest testChunking abcdef 2 4 cd:ef
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking "" 0 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking a 0 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking a 1 0 ""
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking a 0 1 a
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking ab 0 1 a
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking ab 1 1 b
* @run main/othervm -Djdk.httpclient.bufsize=3 ${test.main.class} testChunking ab 0 2 ab
* @run main/othervm -Djdk.httpclient.bufsize=1 ${test.main.class} testChunking abc 0 3 a:b:c
* @run main/othervm -Djdk.httpclient.bufsize=2 ${test.main.class} testChunking abc 0 3 ab:c
* @run main/othervm -Djdk.httpclient.bufsize=2 ${test.main.class} testChunking abcdef 2 4 cd:ef
*/
public class OfByteArrayTest {
public class OfByteArrayTest extends ReplayTestSupport {
private static final Charset CHARSET = StandardCharsets.US_ASCII;
@Test
void testNullContent() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofByteArray(null));
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofByteArray(null, 1, 2));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofByteArray(null));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofByteArray(null, 1, 2));
}
@ParameterizedTest
@ -78,7 +83,15 @@ public class OfByteArrayTest {
byte[] content = contentText.getBytes(CHARSET);
assertThrows(
IndexOutOfBoundsException.class,
() -> HttpRequest.BodyPublishers.ofByteArray(content, offset, length));
() -> BodyPublishers.ofByteArray(content, offset, length));
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
byte[] content = "this content needs to be replayed again and again".getBytes(CHARSET);
ByteBuffer expectedBuffer = ByteBuffer.wrap(content);
BodyPublisher publisher = BodyPublishers.ofByteArray(content);
return List.of(new ReplayTarget(expectedBuffer, publisher));
}
/**
@ -105,7 +118,7 @@ public class OfByteArrayTest {
// Create the publisher
byte[] content = contentText.getBytes(CHARSET);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArray(content, offset, length);
BodyPublisher publisher = BodyPublishers.ofByteArray(content, offset, length);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,7 +27,8 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
@ -46,15 +47,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* @test
* @bug 8226303 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::ofByteArrays` behavior
*
* @build ByteBufferUtils
* RecordingSubscriber
* ReplayTestSupport
*
* @run junit OfByteArraysTest
*
* @comment Using `main/othervm` to initiate tests that depend on a custom-configured JVM
* @run main/othervm -Xmx64m OfByteArraysTest testOOM
*/
public class OfByteArraysTest {
public class OfByteArraysTest extends ReplayTestSupport {
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
@ -65,7 +69,7 @@ public class OfByteArraysTest {
.range(0, length)
.mapToObj(i -> new byte[]{(byte) i})
.toList();
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(buffers::iterator);
BodyPublisher publisher = BodyPublishers.ofByteArrays(buffers::iterator);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -95,7 +99,7 @@ public class OfByteArraysTest {
case 2 -> List.of(buffer2).iterator();
default -> throw new AssertionError();
};
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(iterable);
BodyPublisher publisher = BodyPublishers.ofByteArrays(iterable);
// Subscribe twice (to force two `Iterable::iterator` invocations)
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -112,14 +116,14 @@ public class OfByteArraysTest {
@Test
void testNullIterable() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofByteArrays(null));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofByteArrays(null));
}
@Test
void testNullIterator() throws InterruptedException {
// Create the publisher
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(() -> null);
BodyPublisher publisher = BodyPublishers.ofByteArrays(() -> null);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -138,7 +142,7 @@ public class OfByteArraysTest {
// Create the publisher
List<byte[]> iterable = new ArrayList<>();
iterable.add(null);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(iterable);
BodyPublisher publisher = BodyPublishers.ofByteArrays(iterable);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -156,7 +160,7 @@ public class OfByteArraysTest {
// Create the publisher
RuntimeException exception = new RuntimeException("failure for `testIteratorCreationException`");
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(() -> {
BodyPublisher publisher = BodyPublishers.ofByteArrays(() -> {
throw exception;
});
@ -192,7 +196,7 @@ public class OfByteArraysTest {
// Create the publisher
IteratorThrowingAtEnd iterator =
new IteratorThrowingAtEnd(exceptionIndex, hasNextException, nextException);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(() -> iterator);
BodyPublisher publisher = BodyPublishers.ofByteArrays(() -> iterator);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -257,6 +261,14 @@ public class OfByteArraysTest {
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
byte[] byteArray = ByteBufferUtils.byteArrayOfLength(9);
ByteBuffer expectedBuffer = ByteBuffer.wrap(byteArray);
BodyPublisher publisher = BodyPublishers.ofByteArrays(List.of(byteArray));
return List.of(new ReplayTarget(expectedBuffer, -1, publisher, null));
}
/**
* Initiates tests that depend on a custom-configured JVM.
*/
@ -273,7 +285,7 @@ public class OfByteArraysTest {
// Create the publisher
int length = ByteBufferUtils.findLengthExceedingMaxMemory();
Iterable<byte[]> iterable = createIterableOfLength(length);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofByteArrays(iterable);
BodyPublisher publisher = BodyPublishers.ofByteArrays(iterable);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,9 +26,12 @@
* @summary Verifies `HttpRequest.BodyPublishers::ofFileChannel`
* @library /test/lib
* /test/jdk/java/net/httpclient/lib
* @build jdk.httpclient.test.lib.common.HttpServerAdapters
* @build ByteBufferUtils
* RecordingSubscriber
* ReplayTestSupport
* jdk.httpclient.test.lib.common.HttpServerAdapters
* jdk.test.lib.net.SimpleSSLContext
* @run junit FileChannelPublisherTest
* @run junit ${test.main.class}
*/
import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestHandler;
@ -52,8 +55,10 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
@ -82,9 +87,9 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class FileChannelPublisherTest {
class OfFileChannelTest extends ReplayTestSupport {
private static final String CLASS_NAME = FileChannelPublisherTest.class.getSimpleName();
private static final String CLASS_NAME = OfFileChannelTest.class.getSimpleName();
private static final Logger LOGGER = Utils.getDebugLogger(CLASS_NAME::toString, Utils.DEBUG);
@ -650,6 +655,22 @@ class FileChannelPublisherTest {
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
int fileLength = 42;
byte[] fileBytes = generateFileBytes(fileLength);
Path filePath = Path.of("replayTarget.txt");
try {
Files.write(filePath, fileBytes, StandardOpenOption.CREATE);
FileChannel fileChannel = FileChannel.open(filePath);
BodyPublisher publisher = BodyPublishers.ofFileChannel(fileChannel, 0, fileLength);
ByteBuffer expectedBuffer = ByteBuffer.wrap(fileBytes);
return List.of(new ReplayTarget(expectedBuffer, fileLength, publisher, fileChannel));
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
/**
* Performs the initial {@code HEAD} request to the specified server. This
* effectively admits a connection to the client's pool, where all protocol

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -31,7 +31,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
@ -51,15 +52,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* @test
* @bug 8226303 8235459 8358688 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::ofFile` behavior
*
* @build ByteBufferUtils
* RecordingSubscriber
* @run junit OfFileTest
* ReplayTestSupport
*
* @run junit ${test.main.class}
*
* @comment Using `main/othervm` to initiate tests that depend on a custom-configured JVM
* @run main/othervm -Xmx64m OfFileTest testOOM
* @run main/othervm -Xmx64m ${test.main.class} testOOM
*/
public class OfFileTest {
public class OfFileTest extends ReplayTestSupport {
private static final Path DEFAULT_FS_DIR = Path.of(System.getProperty("user.dir", "."));
@ -89,14 +93,14 @@ public class OfFileTest {
@Test
void testNullPath() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofFile(null));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofFile(null));
}
@ParameterizedTest
@MethodSource("parentDirs")
void testNonExistentPath(Path parentDir) {
Path nonExistentPath = createFilePath(parentDir, "testNonExistentPath");
assertThrows(FileNotFoundException.class, () -> HttpRequest.BodyPublishers.ofFile(nonExistentPath));
assertThrows(FileNotFoundException.class, () -> BodyPublishers.ofFile(nonExistentPath));
}
@ParameterizedTest
@ -106,7 +110,7 @@ public class OfFileTest {
// Create the publisher
byte[] fileBytes = ByteBufferUtils.byteArrayOfLength(3);
Path filePath = createFile(parentDir, "testNonExistentPathAtSubscribe", fileBytes);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofFile(filePath);
BodyPublisher publisher = BodyPublishers.ofFile(filePath);
// Delete the file
Files.delete(filePath);
@ -131,7 +135,7 @@ public class OfFileTest {
void testIrregularFile(Path parentDir) throws Exception {
// Create the publisher
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofFile(parentDir);
BodyPublisher publisher = BodyPublishers.ofFile(parentDir);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -167,7 +171,7 @@ public class OfFileTest {
// Create the publisher
byte[] fileBytes = ByteBufferUtils.byteArrayOfLength(BIG_FILE_LENGTH);
Path filePath = createFile(parentDir, "testFileModificationWhileReading", fileBytes);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofFile(filePath);
BodyPublisher publisher = BodyPublishers.ofFile(filePath);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -210,7 +214,7 @@ public class OfFileTest {
// Create the publisher
byte[] fileBytes = ByteBufferUtils.byteArrayOfLength(fileLength);
Path filePath = createFile(parentDir, "testFileOfLength", fileBytes);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofFile(filePath);
BodyPublisher publisher = BodyPublishers.ofFile(filePath);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -222,6 +226,24 @@ public class OfFileTest {
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
byte[] fileBytes = ByteBufferUtils.byteArrayOfLength(3);
ByteBuffer expectedBuffer = ByteBuffer.wrap(fileBytes);
return parentDirs()
.stream()
.map(parentDir -> {
try {
Path filePath = createFile(parentDir, "replayTest", fileBytes);
BodyPublisher publisher = BodyPublishers.ofFile(filePath);
return new ReplayTarget(expectedBuffer, publisher);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
})
.toList();
}
/**
* Initiates tests that depend on a custom-configured JVM.
*/
@ -248,7 +270,7 @@ public class OfFileTest {
// Create the publisher
int fileLength = ByteBufferUtils.findLengthExceedingMaxMemory();
Path filePath = createFileOfLength(parentDir, "testOOM", fileLength);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofFile(filePath);
BodyPublisher publisher = BodyPublishers.ofFile(filePath);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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,7 +28,9 @@ import org.junit.jupiter.params.provider.ValueSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.Flow;
import java.util.function.Supplier;
@ -41,19 +43,22 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* @test
* @bug 8364733
* @summary Verify all specified `HttpRequest.BodyPublishers::ofInputStream` behavior
*
* @build ByteBufferUtils
* RecordingSubscriber
* @run junit OfInputStreamTest
* ReplayTestSupport
*
* @run junit ${test.main.class}
*
* @comment Using `main/othervm` to initiate tests that depend on a custom-configured JVM
* @run main/othervm -Xmx64m OfInputStreamTest testOOM
* @run main/othervm -Xmx64m ${test.main.class} testOOM
*/
public class OfInputStreamTest {
public class OfInputStreamTest extends ReplayTestSupport {
@Test
void testNullInputStreamSupplier() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofInputStream(null));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofInputStream(null));
}
@Test
@ -61,7 +66,7 @@ public class OfInputStreamTest {
// Create the publisher
RuntimeException exception = new RuntimeException();
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofInputStream(() -> { throw exception; });
BodyPublisher publisher = BodyPublishers.ofInputStream(() -> { throw exception; });
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -80,7 +85,7 @@ public class OfInputStreamTest {
void testNullInputStream() throws InterruptedException {
// Create the publisher
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofInputStream(() -> null);
BodyPublisher publisher = BodyPublishers.ofInputStream(() -> null);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -107,7 +112,7 @@ public class OfInputStreamTest {
case 2 -> new ByteArrayInputStream(buffer2);
default -> throw new AssertionError();
};
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofInputStream(inputStreamSupplier);
BodyPublisher publisher = BodyPublishers.ofInputStream(inputStreamSupplier);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -129,7 +134,7 @@ public class OfInputStreamTest {
// Create the publisher
byte[] content = ByteBufferUtils.byteArrayOfLength(length);
InputStream inputStream = new ByteArrayInputStream(content);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofInputStream(() -> inputStream);
BodyPublisher publisher = BodyPublishers.ofInputStream(() -> inputStream);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -148,7 +153,7 @@ public class OfInputStreamTest {
// Create the publisher
RuntimeException exception = new RuntimeException("failure for `read`");
InputStream inputStream = new InputStreamThrowingOnCompletion(exceptionIndex, exception);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofInputStream(() -> inputStream);
BodyPublisher publisher = BodyPublishers.ofInputStream(() -> inputStream);
// Subscribe
RecordingSubscriber subscriber = new RecordingSubscriber();
@ -185,6 +190,14 @@ public class OfInputStreamTest {
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
byte[] content = ByteBufferUtils.byteArrayOfLength(10);
ByteBuffer expectedBuffer = ByteBuffer.wrap(content);
BodyPublisher publisher = BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(content));
return List.of(new ReplayTarget(expectedBuffer, -1, publisher, null));
}
/**
* Initiates tests that depend on a custom-configured JVM.
*/
@ -200,8 +213,8 @@ public class OfInputStreamTest {
// Create the publisher using an `InputStream` that emits content exceeding the maximum memory
int length = ByteBufferUtils.findLengthExceedingMaxMemory();
HttpRequest.BodyPublisher publisher =
HttpRequest.BodyPublishers.ofInputStream(() -> new InputStream() {
BodyPublisher publisher =
BodyPublishers.ofInputStream(() -> new InputStream() {
private int position;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,10 +26,12 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.Flow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -41,10 +43,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* @summary Verify all specified `HttpRequest.BodyPublishers::ofString` behavior
* @build ByteBufferUtils
* RecordingSubscriber
* @run junit OfStringTest
* ReplayTestSupport
* @run junit ${test.main.class}
*/
class OfStringTest {
class OfStringTest extends ReplayTestSupport {
private static final Charset CHARSET = StandardCharsets.US_ASCII;
@ -58,7 +61,7 @@ class OfStringTest {
contentChars[i] = (char) ('a' + i);
}
String content = new String(contentChars);
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofString(content, CHARSET);
BodyPublisher publisher = BodyPublishers.ofString(content, CHARSET);
// Subscribe
assertEquals(length, publisher.contentLength());
@ -87,7 +90,7 @@ class OfStringTest {
void testCharset(String content, Charset charset) throws InterruptedException {
// Create the publisher
HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofString(content, charset);
BodyPublisher publisher = BodyPublishers.ofString(content, charset);
// Subscribe
ByteBuffer expectedBuffer = charset.encode(content);
@ -108,12 +111,20 @@ class OfStringTest {
@Test
void testNullContent() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofString(null, CHARSET));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofString(null, CHARSET));
}
@Test
void testNullCharset() {
assertThrows(NullPointerException.class, () -> HttpRequest.BodyPublishers.ofString("foo", null));
assertThrows(NullPointerException.class, () -> BodyPublishers.ofString("foo", null));
}
@Override
Iterable<ReplayTarget> createReplayTargets() {
String content = "this content needs to be replayed again and again";
ByteBuffer expectedBuffer = CHARSET.encode(content);
BodyPublisher publisher = BodyPublishers.ofString(content, CHARSET);
return List.of(new ReplayTarget(expectedBuffer, publisher));
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2026, 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 org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.net.http.HttpRequest.BodyPublisher;
import java.nio.ByteBuffer;
import java.util.concurrent.Flow;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests for verifying that a request body publisher supports multiple subscriptions, aka. replayable.
*/
public abstract class ReplayTestSupport {
@ParameterizedTest
@ValueSource(strings = {
// 2 subscriptions
"subscribe-cancel-subscribe-cancel",
"subscribe-cancel-subscribe-request",
"subscribe-request-subscribe-cancel",
"subscribe-request-subscribe-request",
// 3 subscriptions
"subscribe-cancel-subscribe-cancel-subscribe-request",
"subscribe-cancel-subscribe-request-subscribe-cancel",
"subscribe-cancel-subscribe-request-subscribe-request",
"subscribe-request-subscribe-cancel-subscribe-cancel",
"subscribe-request-subscribe-cancel-subscribe-request",
"subscribe-request-subscribe-request-subscribe-cancel",
"subscribe-request-subscribe-request-subscribe-request",
})
void testReplay(String opSequence) throws Exception {
for (ReplayTarget replayTarget : createReplayTargets()) {
try (replayTarget) {
System.err.printf("Executing test for replay target: %s%n", replayTarget);
testReplay(opSequence, replayTarget);
}
}
}
private static void testReplay(String opSequence, ReplayTarget replayTarget) throws InterruptedException {
// Create the publisher
ByteBuffer expectedBuffer = replayTarget.expectedBuffer;
BodyPublisher publisher = replayTarget.publisher;
assertEquals(replayTarget.expectedContentLength, publisher.contentLength());
// Execute the specified operations
RecordingSubscriber subscriber = null;
Flow.Subscription subscription = null;
String[] ops = opSequence.split("-");
for (int opIndex = 0; opIndex < ops.length; opIndex++) {
String op = ops[opIndex];
System.err.printf("Executing operation at index %s: %s%n", opIndex, op);
switch (op) {
case "subscribe": {
subscriber = new RecordingSubscriber();
publisher.subscribe(subscriber);
assertEquals("onSubscribe", subscriber.invocations.take());
subscription = (Flow.Subscription) subscriber.invocations.take();
break;
}
case "request": {
assert subscription != null;
subscription.request(Long.MAX_VALUE);
if (expectedBuffer.hasRemaining()) {
assertEquals("onNext", subscriber.invocations.take());
ByteBuffer actualBuffer = (ByteBuffer) subscriber.invocations.take();
ByteBufferUtils.assertEquals(expectedBuffer, actualBuffer, null);
}
assertEquals("onComplete", subscriber.invocations.take());
break;
}
case "cancel": {
assert subscription != null;
subscription.cancel();
break;
}
default: throw new IllegalArgumentException("Unknown operation: " + op);
}
}
}
abstract Iterable<ReplayTarget> createReplayTargets();
public record ReplayTarget(
ByteBuffer expectedBuffer,
int expectedContentLength,
BodyPublisher publisher,
AutoCloseable resource)
implements AutoCloseable {
public ReplayTarget(ByteBuffer expectedBuffer, BodyPublisher publisher) {
this(expectedBuffer, expectedBuffer.limit(), publisher, null);
}
@Override
public void close() throws Exception {
if (resource != null) {
resource.close();
}
}
}
}