parent
883f123583
commit
dea4f71846
|
|
@ -36,6 +36,7 @@ import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Flow;
|
import java.util.concurrent.Flow;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
@ -94,38 +95,25 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("NullAway")
|
@SuppressWarnings("NullAway")
|
||||||
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
|
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
|
||||||
|
CompletableFuture<HttpResponse<InputStream>> responseFuture = null;
|
||||||
|
try {
|
||||||
HttpRequest request = buildRequest(headers, body);
|
HttpRequest request = buildRequest(headers, body);
|
||||||
CompletableFuture<HttpResponse<InputStream>> responsefuture =
|
responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||||
this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
|
|
||||||
try {
|
|
||||||
if (this.timeout != null) {
|
if (this.timeout != null) {
|
||||||
CompletableFuture<Void> timeoutFuture = new CompletableFuture<Void>()
|
TimeoutHandler timeoutHandler = new TimeoutHandler(responseFuture, this.timeout);
|
||||||
.completeOnTimeout(null, this.timeout.toMillis(), TimeUnit.MILLISECONDS);
|
HttpResponse<InputStream> response = responseFuture.get();
|
||||||
timeoutFuture.thenRun(() -> {
|
InputStream inputStream = timeoutHandler.wrapInputStream(response);
|
||||||
if (!responsefuture.cancel(true) && !responsefuture.isCompletedExceptionally()) {
|
return new JdkClientHttpResponse(response, inputStream);
|
||||||
try {
|
|
||||||
responsefuture.resultNow().body().close();
|
|
||||||
} catch (IOException ignored) {}
|
|
||||||
}
|
}
|
||||||
});
|
else {
|
||||||
var response = responsefuture.get();
|
HttpResponse<InputStream> response = responseFuture.get();
|
||||||
return new JdkClientHttpResponse(response.statusCode(), response.headers(), new FilterInputStream(response.body()) {
|
return new JdkClientHttpResponse(response, response.body());
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
timeoutFuture.cancel(false);
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var response = responsefuture.get();
|
|
||||||
return new JdkClientHttpResponse(response.statusCode(), response.headers(), response.body());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException ex) {
|
catch (InterruptedException ex) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
responsefuture.cancel(true);
|
responseFuture.cancel(true);
|
||||||
throw new IOException("Request was interrupted: " + ex.getMessage(), ex);
|
throw new IOException("Request was interrupted: " + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
catch (ExecutionException ex) {
|
catch (ExecutionException ex) {
|
||||||
|
|
@ -149,7 +137,6 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) {
|
private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) {
|
||||||
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(this.uri);
|
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(this.uri);
|
||||||
|
|
||||||
|
|
@ -225,4 +212,52 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary workaround to use instead of {@link HttpRequest.Builder#timeout(Duration)}
|
||||||
|
* until <a href="https://bugs.openjdk.org/browse/JDK-8258397">JDK-8258397</a>
|
||||||
|
* is fixed. Essentially, create a future wiht a timeout handler, and use it
|
||||||
|
* to close the response.
|
||||||
|
* @see <a href="https://mail.openjdk.org/pipermail/net-dev/2021-October/016672.html">OpenJDK discussion thread</a>
|
||||||
|
*/
|
||||||
|
private static final class TimeoutHandler {
|
||||||
|
|
||||||
|
private final CompletableFuture<Void> timeoutFuture;
|
||||||
|
|
||||||
|
private TimeoutHandler(CompletableFuture<HttpResponse<InputStream>> future, Duration timeout) {
|
||||||
|
|
||||||
|
this.timeoutFuture = new CompletableFuture<Void>()
|
||||||
|
.completeOnTimeout(null, timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
this.timeoutFuture.thenRun(() -> {
|
||||||
|
if (future.cancel(true) || future.isCompletedExceptionally() || !future.isDone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
future.get().body().close();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public InputStream wrapInputStream(HttpResponse<InputStream> response) {
|
||||||
|
InputStream body = response.body();
|
||||||
|
if (body == null) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
return new FilterInputStream(body) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
TimeoutHandler.this.timeoutFuture.cancel(false);
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.http.client;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -26,6 +27,7 @@ import java.util.Map;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.HttpStatusCode;
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
@ -40,21 +42,21 @@ import org.springframework.util.StreamUtils;
|
||||||
*/
|
*/
|
||||||
class JdkClientHttpResponse implements ClientHttpResponse {
|
class JdkClientHttpResponse implements ClientHttpResponse {
|
||||||
|
|
||||||
private final int statusCode;
|
private final HttpResponse<InputStream> response;
|
||||||
|
|
||||||
private final HttpHeaders headers;
|
private final HttpHeaders headers;
|
||||||
|
|
||||||
private final InputStream body;
|
private final InputStream body;
|
||||||
|
|
||||||
|
|
||||||
public JdkClientHttpResponse(int statusCode, java.net.http.HttpHeaders headers, InputStream body) {
|
public JdkClientHttpResponse(HttpResponse<InputStream> response, @Nullable InputStream body) {
|
||||||
this.statusCode = statusCode;
|
this.response = response;
|
||||||
this.headers = adaptHeaders(headers);
|
this.headers = adaptHeaders(response);
|
||||||
this.body = body != null ? body : InputStream.nullInputStream();
|
this.body = (body != null ? body : InputStream.nullInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpHeaders adaptHeaders(java.net.http.HttpHeaders headers) {
|
private static HttpHeaders adaptHeaders(HttpResponse<?> response) {
|
||||||
Map<String, List<String>> rawHeaders = headers.map();
|
Map<String, List<String>> rawHeaders = response.headers().map();
|
||||||
Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ENGLISH);
|
Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ENGLISH);
|
||||||
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
|
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
|
||||||
multiValueMap.putAll(rawHeaders);
|
multiValueMap.putAll(rawHeaders);
|
||||||
|
|
@ -64,7 +66,7 @@ class JdkClientHttpResponse implements ClientHttpResponse {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpStatusCode getStatusCode() {
|
public HttpStatusCode getStatusCode() {
|
||||||
return HttpStatusCode.valueOf(statusCode);
|
return HttpStatusCode.valueOf(this.response.statusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue