Skip to content

Commit 3114913

Browse files
authored
Add timeout support to AbstractOrigin.URIOrigin.getInputStream(OpenOption...) (#831)
* [IO-856] Try test on all OSs for GitHub CI * Add timeout support to AbstractOrigin.URIOrigin with URIOpenOption. Better test fixtures
1 parent 3271675 commit 3114913

2 files changed

Lines changed: 169 additions & 11 deletions

File tree

src/main/java/org/apache/commons/io/build/AbstractOrigin.java

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.Reader;
3232
import java.io.Writer;
3333
import java.net.URI;
34+
import java.net.URLConnection;
3435
import java.nio.channels.Channel;
3536
import java.nio.channels.Channels;
3637
import java.nio.channels.FileChannel;
@@ -43,8 +44,10 @@
4344
import java.nio.file.Path;
4445
import java.nio.file.Paths;
4546
import java.nio.file.StandardOpenOption;
47+
import java.time.Duration;
4648
import java.util.Arrays;
4749
import java.util.Objects;
50+
import java.util.stream.Stream;
4851

4952
import org.apache.commons.io.Charsets;
5053
import org.apache.commons.io.IORandomAccessFile;
@@ -961,6 +964,80 @@ public Reader getReader(final Charset charset) throws IOException {
961964
*/
962965
public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
963966

967+
/**
968+
* Options for connect and read from a URI.
969+
*
970+
* @since 2.22.0
971+
*/
972+
public static final class URIOpenOption implements OpenOption {
973+
974+
/**
975+
* Builds URIOpenOption.
976+
*/
977+
public static class Builder extends AbstractSupplier<URIOpenOption, Builder> {
978+
979+
private Duration connectTimeout;
980+
private Duration readTimeout;
981+
982+
/**
983+
* Constructs a new instance.
984+
*/
985+
public Builder() {
986+
// empty
987+
}
988+
989+
@Override
990+
public URIOpenOption get() {
991+
return new URIOpenOption(this);
992+
}
993+
994+
/**
995+
* Sets the connect timeout duration.
996+
*
997+
* @param connectTimeout the connect timeout duration.
998+
* @return {@code this instance}.
999+
*/
1000+
public Builder setConnectTimeout(final Duration connectTimeout) {
1001+
this.connectTimeout = connectTimeout;
1002+
return asThis();
1003+
}
1004+
1005+
/**
1006+
* Sets the read timeout duration.
1007+
*
1008+
* @param readTimeout the read timeout duration.
1009+
* @return {@code this instance}.
1010+
*/
1011+
public Builder setReadTimeout(final Duration readTimeout) {
1012+
this.readTimeout = readTimeout;
1013+
return asThis();
1014+
}
1015+
}
1016+
1017+
/**
1018+
* Creates a new builder.
1019+
*
1020+
* @return a new builder.
1021+
*/
1022+
public static Builder builder() {
1023+
return new Builder();
1024+
}
1025+
1026+
private final Duration connectTimeout;
1027+
1028+
private final Duration readTimeout;
1029+
1030+
private URIOpenOption(final Builder builder) {
1031+
connectTimeout = builder.connectTimeout;
1032+
readTimeout = builder.readTimeout;
1033+
}
1034+
1035+
@Override
1036+
public String toString() {
1037+
return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]";
1038+
}
1039+
}
1040+
9641041
private static final String SCHEME_HTTPS = "https";
9651042
private static final String SCHEME_HTTP = "http";
9661043

@@ -974,12 +1051,17 @@ public URIOrigin(final URI origin) {
9741051
super(origin);
9751052
}
9761053

1054+
/**
1055+
* {@inheritDoc}
1056+
*
1057+
* @see URIOpenOption
1058+
*/
9771059
@Override
9781060
protected Channel getChannel(final OpenOption... options) throws IOException {
9791061
final URI uri = get();
9801062
final String scheme = uri.getScheme();
9811063
if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
982-
return Channels.newChannel(uri.toURL().openStream());
1064+
return Channels.newChannel(getInputStream(uri, options));
9831065
}
9841066
return Files.newByteChannel(getPath(), options);
9851067
}
@@ -989,20 +1071,52 @@ public File getFile() {
9891071
return getPath().toFile();
9901072
}
9911073

1074+
/**
1075+
* {@inheritDoc}
1076+
* <p>
1077+
* Set timeouts with a {@link URIOpenOption}.
1078+
* </p>
1079+
*
1080+
* @see URIOpenOption
1081+
* @see URLConnection#setConnectTimeout(int)
1082+
* @see URLConnection#setReadTimeout(int)
1083+
*/
9921084
@Override
9931085
public InputStream getInputStream(final OpenOption... options) throws IOException {
9941086
final URI uri = get();
9951087
final String scheme = uri.getScheme();
9961088
if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
997-
return uri.toURL().openStream();
1089+
return getInputStream(uri, options);
9981090
}
9991091
return Files.newInputStream(getPath(), options);
10001092
}
10011093

1094+
private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException {
1095+
final URLConnection connection = uri.toURL().openConnection();
1096+
if (options != null) {
1097+
Stream.of(options).forEach(option -> {
1098+
if (option instanceof URIOpenOption) {
1099+
final URIOpenOption connOption = (URIOpenOption) option;
1100+
if (connOption.connectTimeout != null) {
1101+
connection.setConnectTimeout(toMillis(connOption.connectTimeout));
1102+
}
1103+
if (connOption.readTimeout != null) {
1104+
connection.setReadTimeout(toMillis(connOption.readTimeout));
1105+
}
1106+
}
1107+
});
1108+
}
1109+
return connection.getInputStream();
1110+
}
1111+
10021112
@Override
10031113
public Path getPath() {
10041114
return Paths.get(get());
10051115
}
1116+
1117+
private int toMillis(final Duration duration) {
1118+
return Math.toIntExact(duration.toMillis());
1119+
}
10061120
}
10071121

10081122
/**

src/test/java/org/apache/commons/io/build/URIOriginTest.java

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,22 @@
2121
import java.io.IOException;
2222
import java.io.InputStream;
2323
import java.net.URI;
24+
import java.nio.ByteBuffer;
25+
import java.nio.channels.Channel;
26+
import java.nio.channels.ReadableByteChannel;
2427
import java.nio.file.Files;
2528
import java.nio.file.Path;
2629
import java.nio.file.Paths;
2730
import java.nio.file.StandardOpenOption;
31+
import java.time.Duration;
32+
import java.util.function.Supplier;
2833

2934
import org.apache.commons.io.build.AbstractOrigin.URIOrigin;
35+
import org.apache.commons.io.build.AbstractOrigin.URIOrigin.URIOpenOption;
3036
import org.apache.commons.lang3.ArrayUtils;
3137
import org.junit.jupiter.api.Test;
3238
import org.junit.jupiter.params.ParameterizedTest;
33-
import org.junit.jupiter.params.provider.ValueSource;
39+
import org.junit.jupiter.params.provider.MethodSource;
3440

3541
/**
3642
* Tests {@link URIOrigin}.
@@ -41,6 +47,31 @@
4147
*/
4248
class URIOriginTest extends AbstractOriginTest<URI, URIOrigin> {
4349

50+
// @formatter:off
51+
private static final URIOpenOption URI_OPEN_OPTION = URIOpenOption.builder()
52+
.setConnectTimeout(Duration.ofSeconds(60))
53+
.setReadTimeout(Duration.ofSeconds(60))
54+
.get();
55+
// @formatter:on
56+
57+
static String[] fixtures() {
58+
return new String[] { "http://1.1.1.1", // IP
59+
"http://google.com", // HTTP
60+
"https://apache.org" // HTTPS
61+
};
62+
}
63+
64+
private void checkRead(final InputStream in) throws IOException {
65+
assertNotEquals(-1, in.read());
66+
}
67+
68+
private void checkRead(final Channel in, final Supplier<String> message) throws IOException {
69+
if (in instanceof ReadableByteChannel) {
70+
final ReadableByteChannel rbc = (ReadableByteChannel) in;
71+
assertNotEquals(-1, rbc.read(ByteBuffer.allocate(1)), message);
72+
}
73+
}
74+
4475
@Override
4576
protected URIOrigin newOriginRo() {
4677
return new URIOrigin(Paths.get(FILE_NAME_RO).toUri());
@@ -58,23 +89,36 @@ protected void resetOriginRw() throws IOException {
5889
Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, StandardOpenOption.CREATE);
5990
}
6091

92+
@Test
93+
void testGetChannelFileURI() throws Exception {
94+
final AbstractOrigin.URIOrigin origin = getOriginRo().asThis();
95+
try (Channel in = origin.getChannel()) {
96+
checkRead(in, origin::toString);
97+
}
98+
}
99+
61100
@ParameterizedTest
62-
@ValueSource(strings = {
63-
"http://apache.com",
64-
"https://apache.com"
65-
})
66-
void testGetInputStream(final String uri) throws Exception {
101+
@MethodSource("fixtures")
102+
void testGetInputStrea(final String uri) throws Exception {
67103
final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri));
68-
try (InputStream in = origin.getInputStream()) {
69-
assertNotEquals(-1, in.read());
104+
try (Channel in = origin.getChannel(URI_OPEN_OPTION)) {
105+
checkRead(in, uri::toString);
70106
}
71107
}
72108

109+
@ParameterizedTest
110+
@MethodSource("fixtures")
111+
void testGetInputStream(final String uri) throws Exception {
112+
final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri));
113+
try (InputStream in = origin.getInputStream(URI_OPEN_OPTION)) {
114+
checkRead(in);
115+
}
116+
}
73117
@Test
74118
void testGetInputStreamFileURI() throws Exception {
75119
final AbstractOrigin.URIOrigin origin = getOriginRo().asThis();
76120
try (InputStream in = origin.getInputStream()) {
77-
assertNotEquals(-1, in.read());
121+
checkRead(in);
78122
}
79123
}
80124
}

0 commit comments

Comments
 (0)