Skip to content

Commit dea4f71

Browse files
committed
Update contribution
Closes gh-33090
1 parent 883f123 commit dea4f71

File tree

2 files changed

+72
-35
lines changed

2 files changed

+72
-35
lines changed

spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequest.java

+62-27
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.concurrent.Executor;
3737
import java.util.concurrent.Flow;
3838
import java.util.concurrent.TimeUnit;
39+
3940
import org.springframework.http.HttpHeaders;
4041
import org.springframework.http.HttpMethod;
4142
import org.springframework.lang.Nullable;
@@ -94,38 +95,25 @@ public URI getURI() {
9495
@Override
9596
@SuppressWarnings("NullAway")
9697
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
97-
HttpRequest request = buildRequest(headers, body);
98-
CompletableFuture<HttpResponse<InputStream>> responsefuture =
99-
this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
98+
CompletableFuture<HttpResponse<InputStream>> responseFuture = null;
10099
try {
100+
HttpRequest request = buildRequest(headers, body);
101+
responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
102+
101103
if (this.timeout != null) {
102-
CompletableFuture<Void> timeoutFuture = new CompletableFuture<Void>()
103-
.completeOnTimeout(null, this.timeout.toMillis(), TimeUnit.MILLISECONDS);
104-
timeoutFuture.thenRun(() -> {
105-
if (!responsefuture.cancel(true) && !responsefuture.isCompletedExceptionally()) {
106-
try {
107-
responsefuture.resultNow().body().close();
108-
} catch (IOException ignored) {}
109-
}
110-
});
111-
var response = responsefuture.get();
112-
return new JdkClientHttpResponse(response.statusCode(), response.headers(), new FilterInputStream(response.body()) {
113-
114-
@Override
115-
public void close() throws IOException {
116-
timeoutFuture.cancel(false);
117-
super.close();
118-
}
119-
});
120-
121-
} else {
122-
var response = responsefuture.get();
123-
return new JdkClientHttpResponse(response.statusCode(), response.headers(), response.body());
104+
TimeoutHandler timeoutHandler = new TimeoutHandler(responseFuture, this.timeout);
105+
HttpResponse<InputStream> response = responseFuture.get();
106+
InputStream inputStream = timeoutHandler.wrapInputStream(response);
107+
return new JdkClientHttpResponse(response, inputStream);
108+
}
109+
else {
110+
HttpResponse<InputStream> response = responseFuture.get();
111+
return new JdkClientHttpResponse(response, response.body());
124112
}
125113
}
126114
catch (InterruptedException ex) {
127115
Thread.currentThread().interrupt();
128-
responsefuture.cancel(true);
116+
responseFuture.cancel(true);
129117
throw new IOException("Request was interrupted: " + ex.getMessage(), ex);
130118
}
131119
catch (ExecutionException ex) {
@@ -149,7 +137,6 @@ else if (cause instanceof IOException ioEx) {
149137
}
150138
}
151139

152-
153140
private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) {
154141
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(this.uri);
155142

@@ -225,4 +212,52 @@ public ByteBuffer map(byte[] b, int off, int len) {
225212
}
226213
}
227214

215+
216+
/**
217+
* Temporary workaround to use instead of {@link HttpRequest.Builder#timeout(Duration)}
218+
* until <a href="https://bugs.openjdk.org/browse/JDK-8258397">JDK-8258397</a>
219+
* is fixed. Essentially, create a future wiht a timeout handler, and use it
220+
* to close the response.
221+
* @see <a href="https://mail.openjdk.org/pipermail/net-dev/2021-October/016672.html">OpenJDK discussion thread</a>
222+
*/
223+
private static final class TimeoutHandler {
224+
225+
private final CompletableFuture<Void> timeoutFuture;
226+
227+
private TimeoutHandler(CompletableFuture<HttpResponse<InputStream>> future, Duration timeout) {
228+
229+
this.timeoutFuture = new CompletableFuture<Void>()
230+
.completeOnTimeout(null, timeout.toMillis(), TimeUnit.MILLISECONDS);
231+
232+
this.timeoutFuture.thenRun(() -> {
233+
if (future.cancel(true) || future.isCompletedExceptionally() || !future.isDone()) {
234+
return;
235+
}
236+
try {
237+
future.get().body().close();
238+
}
239+
catch (Exception ex) {
240+
// ignore
241+
}
242+
});
243+
244+
}
245+
246+
@Nullable
247+
public InputStream wrapInputStream(HttpResponse<InputStream> response) {
248+
InputStream body = response.body();
249+
if (body == null) {
250+
return body;
251+
}
252+
return new FilterInputStream(body) {
253+
254+
@Override
255+
public void close() throws IOException {
256+
TimeoutHandler.this.timeoutFuture.cancel(false);
257+
super.close();
258+
}
259+
};
260+
}
261+
}
262+
228263
}

spring-web/src/main/java/org/springframework/http/client/JdkClientHttpResponse.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.net.http.HttpClient;
22+
import java.net.http.HttpResponse;
2223
import java.util.List;
2324
import java.util.Locale;
2425
import java.util.Map;
2526

2627
import org.springframework.http.HttpHeaders;
2728
import org.springframework.http.HttpStatus;
2829
import org.springframework.http.HttpStatusCode;
30+
import org.springframework.lang.Nullable;
2931
import org.springframework.util.CollectionUtils;
3032
import org.springframework.util.LinkedCaseInsensitiveMap;
3133
import org.springframework.util.MultiValueMap;
@@ -40,21 +42,21 @@
4042
*/
4143
class JdkClientHttpResponse implements ClientHttpResponse {
4244

43-
private final int statusCode;
45+
private final HttpResponse<InputStream> response;
4446

4547
private final HttpHeaders headers;
4648

4749
private final InputStream body;
4850

4951

50-
public JdkClientHttpResponse(int statusCode, java.net.http.HttpHeaders headers, InputStream body) {
51-
this.statusCode = statusCode;
52-
this.headers = adaptHeaders(headers);
53-
this.body = body != null ? body : InputStream.nullInputStream();
52+
public JdkClientHttpResponse(HttpResponse<InputStream> response, @Nullable InputStream body) {
53+
this.response = response;
54+
this.headers = adaptHeaders(response);
55+
this.body = (body != null ? body : InputStream.nullInputStream());
5456
}
5557

56-
private static HttpHeaders adaptHeaders(java.net.http.HttpHeaders headers) {
57-
Map<String, List<String>> rawHeaders = headers.map();
58+
private static HttpHeaders adaptHeaders(HttpResponse<?> response) {
59+
Map<String, List<String>> rawHeaders = response.headers().map();
5860
Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ENGLISH);
5961
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
6062
multiValueMap.putAll(rawHeaders);
@@ -64,7 +66,7 @@ private static HttpHeaders adaptHeaders(java.net.http.HttpHeaders headers) {
6466

6567
@Override
6668
public HttpStatusCode getStatusCode() {
67-
return HttpStatusCode.valueOf(statusCode);
69+
return HttpStatusCode.valueOf(this.response.statusCode());
6870
}
6971

7072
@Override

0 commit comments

Comments
 (0)