From ed20f2aa57a8494782e362decb48a3561936c194 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Sun, 10 Nov 2024 08:07:48 -0500 Subject: [PATCH 1/2] [IO-856] Try test on all OSs for GitHub CI --- src/test/java/org/apache/commons/io/FileUtilsListFilesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/apache/commons/io/FileUtilsListFilesTest.java b/src/test/java/org/apache/commons/io/FileUtilsListFilesTest.java index 20e3c8d77a1..6def0148670 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsListFilesTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsListFilesTest.java @@ -238,7 +238,7 @@ public void testListFilesWithDeletion() throws IOException { * Tests IO-856 ListFiles should not fail on vanishing files. */ @Test - @EnabledOnOs(value = OS.WINDOWS) + // @EnabledOnOs(value = OS.WINDOWS) public void testListFilesWithDeletionThreaded() throws ExecutionException, InterruptedException { // test for IO-856 // create random directory in tmp, create the directory if it does not exist From f87e29cd8c5fc86e59570a7e39efa9b6cae4036d Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Mon, 16 Feb 2026 07:51:45 -0500 Subject: [PATCH 2/2] Add timeout support to AbstractOrigin.URIOrigin with URIOpenOption. Better test fixtures --- .../commons/io/build/AbstractOrigin.java | 118 +++++++++++++++++- .../commons/io/build/URIOriginTest.java | 62 +++++++-- 2 files changed, 169 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java index 341c442a657..e64f4ae732d 100644 --- a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java +++ b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java @@ -31,6 +31,7 @@ import java.io.Reader; import java.io.Writer; import java.net.URI; +import java.net.URLConnection; import java.nio.channels.Channel; import java.nio.channels.Channels; import java.nio.channels.FileChannel; @@ -43,8 +44,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.Duration; import java.util.Arrays; import java.util.Objects; +import java.util.stream.Stream; import org.apache.commons.io.Charsets; import org.apache.commons.io.IORandomAccessFile; @@ -961,6 +964,80 @@ public Reader getReader(final Charset charset) throws IOException { */ public static class URIOrigin extends AbstractOrigin { + /** + * Options for connect and read from a URI. + * + * @since 2.22.0 + */ + public static final class URIOpenOption implements OpenOption { + + /** + * Builds URIOpenOption. + */ + public static class Builder extends AbstractSupplier { + + private Duration connectTimeout; + private Duration readTimeout; + + /** + * Constructs a new instance. + */ + public Builder() { + // empty + } + + @Override + public URIOpenOption get() { + return new URIOpenOption(this); + } + + /** + * Sets the connect timeout duration. + * + * @param connectTimeout the connect timeout duration. + * @return {@code this instance}. + */ + public Builder setConnectTimeout(final Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return asThis(); + } + + /** + * Sets the read timeout duration. + * + * @param readTimeout the read timeout duration. + * @return {@code this instance}. + */ + public Builder setReadTimeout(final Duration readTimeout) { + this.readTimeout = readTimeout; + return asThis(); + } + } + + /** + * Creates a new builder. + * + * @return a new builder. + */ + public static Builder builder() { + return new Builder(); + } + + private final Duration connectTimeout; + + private final Duration readTimeout; + + private URIOpenOption(final Builder builder) { + connectTimeout = builder.connectTimeout; + readTimeout = builder.readTimeout; + } + + @Override + public String toString() { + return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]"; + } + } + private static final String SCHEME_HTTPS = "https"; private static final String SCHEME_HTTP = "http"; @@ -974,12 +1051,17 @@ public URIOrigin(final URI origin) { super(origin); } + /** + * {@inheritDoc} + * + * @see URIOpenOption + */ @Override protected Channel getChannel(final OpenOption... options) throws IOException { final URI uri = get(); final String scheme = uri.getScheme(); if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) { - return Channels.newChannel(uri.toURL().openStream()); + return Channels.newChannel(getInputStream(uri, options)); } return Files.newByteChannel(getPath(), options); } @@ -989,20 +1071,52 @@ public File getFile() { return getPath().toFile(); } + /** + * {@inheritDoc} + *

+ * Set timeouts with a {@link URIOpenOption}. + *

+ * + * @see URIOpenOption + * @see URLConnection#setConnectTimeout(int) + * @see URLConnection#setReadTimeout(int) + */ @Override public InputStream getInputStream(final OpenOption... options) throws IOException { final URI uri = get(); final String scheme = uri.getScheme(); if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) { - return uri.toURL().openStream(); + return getInputStream(uri, options); } return Files.newInputStream(getPath(), options); } + private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException { + final URLConnection connection = uri.toURL().openConnection(); + if (options != null) { + Stream.of(options).forEach(option -> { + if (option instanceof URIOpenOption) { + final URIOpenOption connOption = (URIOpenOption) option; + if (connOption.connectTimeout != null) { + connection.setConnectTimeout(toMillis(connOption.connectTimeout)); + } + if (connOption.readTimeout != null) { + connection.setReadTimeout(toMillis(connOption.readTimeout)); + } + } + }); + } + return connection.getInputStream(); + } + @Override public Path getPath() { return Paths.get(get()); } + + private int toMillis(final Duration duration) { + return Math.toIntExact(duration.toMillis()); + } } /** diff --git a/src/test/java/org/apache/commons/io/build/URIOriginTest.java b/src/test/java/org/apache/commons/io/build/URIOriginTest.java index 6fd2043eb12..764585d2ac3 100644 --- a/src/test/java/org/apache/commons/io/build/URIOriginTest.java +++ b/src/test/java/org/apache/commons/io/build/URIOriginTest.java @@ -21,16 +21,22 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.Duration; +import java.util.function.Supplier; import org.apache.commons.io.build.AbstractOrigin.URIOrigin; +import org.apache.commons.io.build.AbstractOrigin.URIOrigin.URIOpenOption; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests {@link URIOrigin}. @@ -41,6 +47,31 @@ */ class URIOriginTest extends AbstractOriginTest { + // @formatter:off + private static final URIOpenOption URI_OPEN_OPTION = URIOpenOption.builder() + .setConnectTimeout(Duration.ofSeconds(60)) + .setReadTimeout(Duration.ofSeconds(60)) + .get(); + // @formatter:on + + static String[] fixtures() { + return new String[] { "http://1.1.1.1", // IP + "http://google.com", // HTTP + "https://apache.org" // HTTPS + }; + } + + private void checkRead(final InputStream in) throws IOException { + assertNotEquals(-1, in.read()); + } + + private void checkRead(final Channel in, final Supplier message) throws IOException { + if (in instanceof ReadableByteChannel) { + final ReadableByteChannel rbc = (ReadableByteChannel) in; + assertNotEquals(-1, rbc.read(ByteBuffer.allocate(1)), message); + } + } + @Override protected URIOrigin newOriginRo() { return new URIOrigin(Paths.get(FILE_NAME_RO).toUri()); @@ -58,23 +89,36 @@ protected void resetOriginRw() throws IOException { Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, StandardOpenOption.CREATE); } + @Test + void testGetChannelFileURI() throws Exception { + final AbstractOrigin.URIOrigin origin = getOriginRo().asThis(); + try (Channel in = origin.getChannel()) { + checkRead(in, origin::toString); + } + } + @ParameterizedTest - @ValueSource(strings = { - "http://apache.com", - "https://apache.com" - }) - void testGetInputStream(final String uri) throws Exception { + @MethodSource("fixtures") + void testGetInputStrea(final String uri) throws Exception { final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri)); - try (InputStream in = origin.getInputStream()) { - assertNotEquals(-1, in.read()); + try (Channel in = origin.getChannel(URI_OPEN_OPTION)) { + checkRead(in, uri::toString); } } + @ParameterizedTest + @MethodSource("fixtures") + void testGetInputStream(final String uri) throws Exception { + final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri)); + try (InputStream in = origin.getInputStream(URI_OPEN_OPTION)) { + checkRead(in); + } + } @Test void testGetInputStreamFileURI() throws Exception { final AbstractOrigin.URIOrigin origin = getOriginRo().asThis(); try (InputStream in = origin.getInputStream()) { - assertNotEquals(-1, in.read()); + checkRead(in); } } }