implement connection and read timeouts for synchronous requests
This commit is contained in:
parent
d3d16e22a0
commit
9e84d9f40d
@ -475,10 +475,17 @@ public class UraClient implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private InputStream request(String url) throws IOException {
|
private InputStream request(String url) throws IOException {
|
||||||
try {
|
try {
|
||||||
return HttpClient.newHttpClient().send(
|
var clientBuilder = HttpClient.newBuilder();
|
||||||
HttpRequest.newBuilder(URI.create(url)).GET().build(),
|
if (config.getConnectTimeout() != null) {
|
||||||
HttpResponse.BodyHandlers.ofInputStream()
|
clientBuilder.connectTimeout(config.getConnectTimeout());
|
||||||
).body();
|
}
|
||||||
|
|
||||||
|
var reqBuilder = HttpRequest.newBuilder(URI.create(url)).GET();
|
||||||
|
if (config.getTimeout() != null) {
|
||||||
|
reqBuilder.timeout(config.getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientBuilder.build().send(reqBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()).body();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
throw new IOException("API request interrupted", e);
|
throw new IOException("API request interrupted", e);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package de.stklcode.pubtrans.ura;
|
package de.stklcode.pubtrans.ura;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configurstion Object for the {@link UraClient}.
|
* Configurstion Object for the {@link UraClient}.
|
||||||
@ -17,6 +18,8 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
private final String baseURL;
|
private final String baseURL;
|
||||||
private final String instantPath;
|
private final String instantPath;
|
||||||
private final String streamPath;
|
private final String streamPath;
|
||||||
|
private final Duration connectTimeout;
|
||||||
|
private final Duration timeout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get new configuration {@link Builder} for given base URL.
|
* Get new configuration {@link Builder} for given base URL.
|
||||||
@ -38,6 +41,8 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
this.baseURL = builder.baseURL;
|
this.baseURL = builder.baseURL;
|
||||||
this.instantPath = builder.instantPath;
|
this.instantPath = builder.instantPath;
|
||||||
this.streamPath = builder.streamPath;
|
this.streamPath = builder.streamPath;
|
||||||
|
this.connectTimeout = builder.connectTimeout;
|
||||||
|
this.timeout = builder.timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,6 +72,24 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
return this.streamPath;
|
return this.streamPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the connection timeout, if any.
|
||||||
|
*
|
||||||
|
* @return Timeout duration or {@code null} if none specified.
|
||||||
|
*/
|
||||||
|
public Duration getConnectTimeout() {
|
||||||
|
return this.connectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the response timeout, if any.
|
||||||
|
*
|
||||||
|
* @return Timeout duration or {@code null} if none specified.
|
||||||
|
*/
|
||||||
|
public Duration getTimeout() {
|
||||||
|
return this.timeout;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for {@link UraClientConfiguration} objects.
|
* Builder for {@link UraClientConfiguration} objects.
|
||||||
*/
|
*/
|
||||||
@ -74,6 +97,8 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
private final String baseURL;
|
private final String baseURL;
|
||||||
private String instantPath;
|
private String instantPath;
|
||||||
private String streamPath;
|
private String streamPath;
|
||||||
|
private Duration connectTimeout;
|
||||||
|
private Duration timeout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the builder with mandatory base URL.
|
* Initialize the builder with mandatory base URL.
|
||||||
@ -85,6 +110,8 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
this.instantPath = DEFAULT_INSTANT_PATH;
|
this.instantPath = DEFAULT_INSTANT_PATH;
|
||||||
this.streamPath = DEFAULT_STREAM_PATH;
|
this.streamPath = DEFAULT_STREAM_PATH;
|
||||||
|
this.connectTimeout = null;
|
||||||
|
this.timeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,6 +136,28 @@ public class UraClientConfiguration implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a custom connection timeout duration.
|
||||||
|
*
|
||||||
|
* @param connectTimeout Timeout duration.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
public Builder withConnectTimeout(Duration connectTimeout) {
|
||||||
|
this.connectTimeout = connectTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a custom timeout duration.
|
||||||
|
*
|
||||||
|
* @param timeout Timeout duration.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
public Builder withTimeout(Duration timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finally build the configuration object.
|
* Finally build the configuration object.
|
||||||
*
|
*
|
||||||
|
@ -29,6 +29,9 @@ import org.junit.jupiter.api.BeforeAll;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.http.HttpConnectTimeoutException;
|
||||||
|
import java.net.http.HttpTimeoutException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -389,6 +392,57 @@ public class UraClientTest {
|
|||||||
assertThat(messages.get(0).getText(), is("Sehr geehrte Fahrgäste, wegen Strassenbauarbeiten kann diese Haltestelle nicht von den Bussen der Linien 17, 44 und N2 angefahren werden."));
|
assertThat(messages.get(0).getText(), is("Sehr geehrte Fahrgäste, wegen Strassenbauarbeiten kann diese Haltestelle nicht von den Bussen der Linien 17, 44 und N2 angefahren werden."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void timeoutTest() throws IOException {
|
||||||
|
// Try to read trips from TEST-NET-1 IP that is not routed (hopefully) and will not connect within 100ms.
|
||||||
|
UraClientException exception = assertThrows(
|
||||||
|
UraClientException.class,
|
||||||
|
() -> new UraClient(
|
||||||
|
UraClientConfiguration.forBaseURL("http://192.0.2.1")
|
||||||
|
.withConnectTimeout(Duration.ofMillis(100))
|
||||||
|
.build()
|
||||||
|
).forDestinationNames("Piccadilly Circus").getTrips(),
|
||||||
|
"Connection to TEST-NET-1 address should fail"
|
||||||
|
);
|
||||||
|
assertTrue(exception.getCause() instanceof HttpConnectTimeoutException, "Exception cause is not HttpConnectionTimeoutException");
|
||||||
|
|
||||||
|
// Mock the HTTP call with delay of 200ms, but immediate connection.
|
||||||
|
WireMock.stubFor(
|
||||||
|
get(urlPathEqualTo("/interfaces/ura/instant_V1")).willReturn(
|
||||||
|
aResponse().withFixedDelay(200).withBodyFile("instant_V1_trips_destination.txt")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assertDoesNotThrow(
|
||||||
|
() -> new UraClient(
|
||||||
|
UraClientConfiguration.forBaseURL(httpMock.baseUrl())
|
||||||
|
.withConnectTimeout(Duration.ofMillis(100))
|
||||||
|
.build()
|
||||||
|
).forDestinationNames("Piccadilly Circus").getTrips(),
|
||||||
|
"Connection timeout should not affect response time."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now specify response timeout.
|
||||||
|
exception = assertThrows(
|
||||||
|
UraClientException.class,
|
||||||
|
() -> new UraClient(
|
||||||
|
UraClientConfiguration.forBaseURL(httpMock.baseUrl())
|
||||||
|
.withTimeout(Duration.ofMillis(100))
|
||||||
|
.build()
|
||||||
|
).forDestinationNames("Piccadilly Circus").getTrips(),
|
||||||
|
"Response timeout did not raise an exception"
|
||||||
|
);
|
||||||
|
assertTrue(exception.getCause() instanceof HttpTimeoutException, "Exception cause is not HttpTimeoutException");
|
||||||
|
|
||||||
|
assertDoesNotThrow(
|
||||||
|
() -> new UraClient(
|
||||||
|
UraClientConfiguration.forBaseURL(httpMock.baseUrl())
|
||||||
|
.withTimeout(Duration.ofMillis(300))
|
||||||
|
.build()
|
||||||
|
).forDestinationNames("Piccadilly Circus").getTrips(),
|
||||||
|
"Response timeout of 300ms with 100ms delay must not fail"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void mockHttpToFile(int version, String resourceFile) {
|
private static void mockHttpToFile(int version, String resourceFile) {
|
||||||
WireMock.stubFor(
|
WireMock.stubFor(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user