parent
							
								
									883f123583
								
							
						
					
					
						commit
						dea4f71846
					
				|  | @ -36,6 +36,7 @@ import java.util.concurrent.ExecutionException; | |||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.Flow; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.lang.Nullable; | ||||
|  | @ -94,38 +95,25 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest { | |||
| 	@Override | ||||
| 	@SuppressWarnings("NullAway") | ||||
| 	protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException { | ||||
| 		CompletableFuture<HttpResponse<InputStream>> responseFuture = null; | ||||
| 		try { | ||||
| 			HttpRequest request = buildRequest(headers, body); | ||||
| 		CompletableFuture<HttpResponse<InputStream>> responsefuture = | ||||
| 				this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()); | ||||
| 		try { | ||||
| 			responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()); | ||||
| 
 | ||||
| 			if (this.timeout != null) { | ||||
| 				CompletableFuture<Void> timeoutFuture = new CompletableFuture<Void>() | ||||
| 						.completeOnTimeout(null, this.timeout.toMillis(), TimeUnit.MILLISECONDS); | ||||
| 				timeoutFuture.thenRun(() -> { | ||||
| 					if (!responsefuture.cancel(true) && !responsefuture.isCompletedExceptionally()) { | ||||
| 						try { | ||||
| 							responsefuture.resultNow().body().close(); | ||||
| 						} catch (IOException ignored) {} | ||||
| 				TimeoutHandler timeoutHandler = new TimeoutHandler(responseFuture, this.timeout); | ||||
| 				HttpResponse<InputStream> response = responseFuture.get(); | ||||
| 				InputStream inputStream = timeoutHandler.wrapInputStream(response); | ||||
| 				return new JdkClientHttpResponse(response, inputStream); | ||||
| 			} | ||||
| 				}); | ||||
| 				var response = responsefuture.get(); | ||||
| 				return new JdkClientHttpResponse(response.statusCode(), response.headers(), new FilterInputStream(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()); | ||||
| 			else { | ||||
| 				HttpResponse<InputStream> response = responseFuture.get(); | ||||
| 				return new JdkClientHttpResponse(response, response.body()); | ||||
| 			} | ||||
| 		} | ||||
| 		catch (InterruptedException ex) { | ||||
| 			Thread.currentThread().interrupt(); | ||||
| 			responsefuture.cancel(true); | ||||
| 			responseFuture.cancel(true); | ||||
| 			throw new IOException("Request was interrupted: " + ex.getMessage(), ex); | ||||
| 		} | ||||
| 		catch (ExecutionException ex) { | ||||
|  | @ -149,7 +137,6 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) { | ||||
| 		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.InputStream; | ||||
| import java.net.http.HttpClient; | ||||
| import java.net.http.HttpResponse; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
|  | @ -26,6 +27,7 @@ import java.util.Map; | |||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.HttpStatusCode; | ||||
| import org.springframework.lang.Nullable; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| import org.springframework.util.LinkedCaseInsensitiveMap; | ||||
| import org.springframework.util.MultiValueMap; | ||||
|  | @ -40,21 +42,21 @@ import org.springframework.util.StreamUtils; | |||
|  */ | ||||
| class JdkClientHttpResponse implements ClientHttpResponse { | ||||
| 
 | ||||
| 	private final int statusCode; | ||||
| 	private final HttpResponse<InputStream> response; | ||||
| 
 | ||||
| 	private final HttpHeaders headers; | ||||
| 
 | ||||
| 	private final InputStream body; | ||||
| 
 | ||||
| 
 | ||||
| 	public JdkClientHttpResponse(int statusCode, java.net.http.HttpHeaders headers, InputStream body) { | ||||
|         this.statusCode = statusCode; | ||||
|         this.headers = adaptHeaders(headers); | ||||
| 		this.body = body != null ? body : InputStream.nullInputStream(); | ||||
| 	public JdkClientHttpResponse(HttpResponse<InputStream> response, @Nullable InputStream body) { | ||||
| 		this.response = response; | ||||
| 		this.headers = adaptHeaders(response); | ||||
| 		this.body = (body != null ? body : InputStream.nullInputStream()); | ||||
| 	} | ||||
| 
 | ||||
| 	private static HttpHeaders adaptHeaders(java.net.http.HttpHeaders headers) { | ||||
| 		Map<String, List<String>> rawHeaders = headers.map(); | ||||
| 	private static HttpHeaders adaptHeaders(HttpResponse<?> response) { | ||||
| 		Map<String, List<String>> rawHeaders = response.headers().map(); | ||||
| 		Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ENGLISH); | ||||
| 		MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map); | ||||
| 		multiValueMap.putAll(rawHeaders); | ||||
|  | @ -64,7 +66,7 @@ class JdkClientHttpResponse implements ClientHttpResponse { | |||
| 
 | ||||
| 	@Override | ||||
| 	public HttpStatusCode getStatusCode() { | ||||
| 		return HttpStatusCode.valueOf(statusCode); | ||||
| 		return HttpStatusCode.valueOf(this.response.statusCode()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue