I've written a starter for the @HttpExchange
HTTP client, which can be referenced at quick-start.
I have now implemented the feature to set individual timeout durations for each client. However, I've noticed in practical scenarios there's often a need to set unique timeout durations for individual requests. For example, consider this UserApi
:
public interface UserApi {
@GetExchange("/users/{id}")
UserDTO getById(@PathVariable("id") String id);
@GetExchange("/users/getByName")
UserDTO getByName(@RequestParam("name") String name);
@GetExchange("/users/getByEmail")
UserDTO getByEmail(@RequestParam("email") String email);
@GetExchange("/users")
List<UserDTO> list();
}
This UserApi
offers many methods, the majority of which should not have an excessively long timeout, perhaps 3 seconds. However, there might be specific methods, such as list
(to get all users), which could be more time-consuming, and for these, I'd like to set a longer timeout, like 10 seconds.
I do not want to set the timeout for the entire client to 10 seconds just because of this list
method; nor do I want to create a separate client just for this list
method, as it indeed is part of the UserApi
.
Therefore, I came up with a solution, by providing a Timeoutable
interface:
public interface Timeoutable<T extends Timeoutable<T>> {
T withTimeout(Duration timeout);
}
public interface UserApi extends Timeoutable<UserApi> {
// same as before...
}
UserApi userApi = ... // Assume UserApi's read-timeout is set to 3 seconds
userApi.list(); // timeout is 3 seconds
userApi.withTimeout(Duration.ofSeconds(10)).list(); // timeout is 10 seconds
I looked into all implementations of AbstractStreamingClientHttpRequest
and found no way to modify the request's timeout duration. I guess the reason for not implementing this feature is that Spring considered that not all HTTP Client implementations support setting timeout durations for individual requests.
I think a magic header could be a solution, where this header's value can override the request's timeout duration. Taking JdkClientHttpRequest
as an example:
class JdkClientHttpRequest extends ShadedAbstractStreamingClientHttpRequest {
// ...
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
try {
HttpRequest request = buildRequest(headers, body);
HttpRequest.Builder builder = HttpRequest.newBuilder(request, (n, v) -> true);
Optional.ofNullable(headers.getFirst("X-HttpExchange-Request-Timeout")) // magic header
.map(Long::valueOf)
.map(Duration::ofMillis)
.ifPresent(builder::timeout);
HttpRequest request = builder.build();
HttpResponse<InputStream> response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
return new ShadedJdkClientHttpResponse(response);
} catch (UncheckedIOException ex) {
throw ex.getCause();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IOException("Could not send request: " + ex.getMessage(), ex);
}
}
// ...
}
For those HTTP Client implementations that don't support setting timeouts for individual requests, this is also one of the reasons why I prefer other HTTP Client implementations :)
Comment From: bclozel
Thanks for the proposal, but we already provide some timeout configuration on the request factory implementations. As you've found out, we don't provide such facility on the request interface directly as clients don't implement the same feature. Some have read timeouts, connect timeouts, timeout for getting a connection from the pool, etc.
The solution you're proposing would not work for connection timeouts as the connection is already established when the request is created.
I'm closing this issue as a result.
Comment From: quaff
The solution you're proposing would not work for connection timeouts as the connection is already established when the request is created.
connection timeout doesn't need personalization, read timeout does.