diff --git a/today-test/src/main/java/cn/taketoday/framework/test/web/client/RootUriRequestExpectationManager.java b/today-test/src/main/java/cn/taketoday/framework/test/web/client/RootUriRequestExpectationManager.java index 7767007c4..411a38378 100644 --- a/today-test/src/main/java/cn/taketoday/framework/test/web/client/RootUriRequestExpectationManager.java +++ b/today-test/src/main/java/cn/taketoday/framework/test/web/client/RootUriRequestExpectationManager.java @@ -22,11 +22,13 @@ import java.net.URI; import java.net.URISyntaxException; import java.time.Duration; +import java.util.concurrent.Executor; import cn.taketoday.http.client.ClientHttpRequest; import cn.taketoday.http.client.ClientHttpResponse; import cn.taketoday.http.client.support.HttpRequestDecorator; import cn.taketoday.lang.Assert; +import cn.taketoday.lang.Nullable; import cn.taketoday.mock.http.client.MockClientHttpRequest; import cn.taketoday.test.web.client.ExpectedCount; import cn.taketoday.test.web.client.MockRestServiceServer; @@ -202,6 +204,11 @@ public Future async() { return getRequest().async(); } + @Override + public Future async(@Nullable Executor executor) { + return getRequest().async(executor); + } + @Override public ClientHttpRequest getRequest() { return (ClientHttpRequest) super.getRequest(); diff --git a/today-test/src/main/java/cn/taketoday/mock/http/client/MockClientHttpRequest.java b/today-test/src/main/java/cn/taketoday/mock/http/client/MockClientHttpRequest.java index 5ce9860e3..a94efc317 100644 --- a/today-test/src/main/java/cn/taketoday/mock/http/client/MockClientHttpRequest.java +++ b/today-test/src/main/java/cn/taketoday/mock/http/client/MockClientHttpRequest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.Executor; import cn.taketoday.http.HttpMethod; import cn.taketoday.http.client.ClientHttpRequest; @@ -113,8 +114,8 @@ public final ClientHttpResponse execute() throws IOException { } @Override - public Future async() { - return Future.run(this::execute); + public Future async(@Nullable Executor executor) { + return Future.run(this::execute, executor); } /** diff --git a/today-web/src/main/java/cn/taketoday/http/client/AbstractClientHttpRequest.java b/today-web/src/main/java/cn/taketoday/http/client/AbstractClientHttpRequest.java index 57a3db458..81f02ac6a 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/AbstractClientHttpRequest.java +++ b/today-web/src/main/java/cn/taketoday/http/client/AbstractClientHttpRequest.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.Executor; import cn.taketoday.http.AbstractHttpRequest; import cn.taketoday.http.HttpHeaders; @@ -72,10 +73,10 @@ public final ClientHttpResponse execute() throws IOException { } @Override - public Future async() { + public Future async(@Nullable Executor executor) { assertNotExecuted(); this.executed = true; - return asyncInternal(headers); + return asyncInternal(headers, executor); } /** @@ -111,9 +112,9 @@ protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) * @param headers the HTTP headers * @return the response object for the executed request */ - protected Future asyncInternal(HttpHeaders headers) { + protected Future asyncInternal(HttpHeaders headers, @Nullable Executor executor) { // todo 这样实现肯定不行 - return Future.run(() -> executeInternal(headers)); + return Future.run(() -> executeInternal(headers), executor); } } diff --git a/today-web/src/main/java/cn/taketoday/http/client/AbstractStreamingClientHttpRequest.java b/today-web/src/main/java/cn/taketoday/http/client/AbstractStreamingClientHttpRequest.java index 18d7b36bd..bd8645d22 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/AbstractStreamingClientHttpRequest.java +++ b/today-web/src/main/java/cn/taketoday/http/client/AbstractStreamingClientHttpRequest.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.Executor; import cn.taketoday.http.HttpHeaders; import cn.taketoday.http.StreamingHttpOutputMessage; @@ -72,11 +73,11 @@ protected final ClientHttpResponse executeInternal(HttpHeaders headers) throws I } @Override - protected final Future asyncInternal(HttpHeaders headers) { + protected final Future asyncInternal(HttpHeaders headers, @Nullable Executor executor) { if (this.body == null && this.bodyStream != null) { this.body = outputStream -> this.bodyStream.writeTo(outputStream); } - return asyncInternal(headers, body); + return asyncInternal(headers, body, executor); } /** @@ -96,9 +97,9 @@ protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, @Null * @param body the HTTP body, may be {@code null} if no body was {@linkplain #setBody(Body) set} * @return the response object for the executed request */ - protected Future asyncInternal(HttpHeaders headers, @Nullable Body body) { + protected Future asyncInternal(HttpHeaders headers, @Nullable Body body, @Nullable Executor executor) { // todo 这样实现肯定不行 - return Future.run(() -> executeInternal(headers, body)); + return Future.run(() -> executeInternal(headers, body), executor); } } diff --git a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpRequest.java b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpRequest.java index 6d6aaf813..6b47bb44b 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpRequest.java +++ b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpRequest.java @@ -18,10 +18,12 @@ package cn.taketoday.http.client; import java.io.IOException; +import java.util.concurrent.Executor; import cn.taketoday.http.HttpMethod; import cn.taketoday.http.HttpOutputMessage; import cn.taketoday.http.HttpRequest; +import cn.taketoday.lang.Nullable; import cn.taketoday.util.concurrent.Future; /** @@ -49,9 +51,35 @@ public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage { /** * Execute this request async, resulting in a {@code Future} that can be read. * + *

The returned future completes exceptionally with: + *

    + *
  • {@link IOException} - if an I/O error occurs when sending or receiving
  • + *
+ * + *

+ * NOT Fully async {@link ClientHttpResponse#getBody()} + * + * @return the async response result of the execution + * @since 5.0 + */ + default Future async() { + return async(null); + } + + /** + * Execute this request async, resulting in a {@code Future} that can be read. + * + *

The returned future completes exceptionally with: + *

    + *
  • {@link IOException} - if an I/O error occurs when sending or receiving
  • + *
+ * + *

+ * NOT Fully async {@link ClientHttpResponse#getBody()} + * * @return the async response result of the execution * @since 5.0 */ - Future async(); + Future async(@Nullable Executor executor); } diff --git a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponse.java b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponse.java index 5ed1da146..e4bc80a6d 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponse.java +++ b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponse.java @@ -1,8 +1,5 @@ /* - * Original Author -> Harry Yang (taketoday@foxmail.com) https://taketoday.cn - * Copyright © Harry Yang & 2017 - 2023 All Rights Reserved. - * - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER + * Copyright 2017 - 2024 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,13 +12,12 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see [http://www.gnu.org/licenses/] + * along with this program. If not, see [https://www.gnu.org/licenses/] */ package cn.taketoday.http.client; import java.io.Closeable; -import java.io.IOException; import cn.taketoday.http.HttpInputMessage; import cn.taketoday.http.HttpStatusCode; @@ -44,20 +40,18 @@ public interface ClientHttpResponse extends HttpInputMessage, Closeable { * Get the HTTP status code as an {@link HttpStatusCode}. * * @return the HTTP status as {@code HttpStatusCode} value (never {@code null}) - * @throws IOException in case of I/O errors */ - HttpStatusCode getStatusCode() throws IOException; + HttpStatusCode getStatusCode(); /** * Get the HTTP status code (potentially non-standard and not * resolvable through the {@link HttpStatusCode} enum) as an integer. * * @return the HTTP status as an integer value - * @throws IOException in case of I/O errors * @see #getStatusCode() * @see HttpStatusCode#valueOf(int) */ - default int getRawStatusCode() throws IOException { + default int getRawStatusCode() { return getStatusCode().value(); } @@ -65,9 +59,8 @@ default int getRawStatusCode() throws IOException { * Get the HTTP status text of the response. * * @return the HTTP status text - * @throws IOException in case of I/O errors */ - String getStatusText() throws IOException; + String getStatusText(); /** * Close this response, freeing any resources created. diff --git a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponseDecorator.java b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponseDecorator.java index 5900721ef..3b7447d7b 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponseDecorator.java +++ b/today-web/src/main/java/cn/taketoday/http/client/ClientHttpResponseDecorator.java @@ -1,8 +1,5 @@ /* - * Original Author -> Harry Yang (taketoday@foxmail.com) https://taketoday.cn - * Copyright © TODAY & 2017 - 2022 All Rights Reserved. - * - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER + * Copyright 2017 - 2024 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +12,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see [http://www.gnu.org/licenses/] + * along with this program. If not, see [https://www.gnu.org/licenses/] */ package cn.taketoday.http.client; @@ -52,17 +49,17 @@ public HttpHeaders getHeaders() { } @Override - public HttpStatusCode getStatusCode() throws IOException { + public HttpStatusCode getStatusCode() { return delegate.getStatusCode(); } @Override - public int getRawStatusCode() throws IOException { + public int getRawStatusCode() { return delegate.getRawStatusCode(); } @Override - public String getStatusText() throws IOException { + public String getStatusText() { return delegate.getStatusText(); } diff --git a/today-web/src/main/java/cn/taketoday/http/client/JdkClientHttpRequest.java b/today-web/src/main/java/cn/taketoday/http/client/JdkClientHttpRequest.java index 5a95a0b85..81473c8a4 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/JdkClientHttpRequest.java +++ b/today-web/src/main/java/cn/taketoday/http/client/JdkClientHttpRequest.java @@ -123,9 +123,9 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body } @Override - protected Future asyncInternal(HttpHeaders headers, @Nullable Body body) { + protected Future asyncInternal(HttpHeaders headers, @Nullable Body body, @Nullable Executor executor) { HttpRequest request = buildRequest(headers, body); - return Future.forAdaption(httpClient.sendAsync(request, BodyHandlers.ofInputStream())) + return Future.forAdaption(httpClient.sendAsync(request, BodyHandlers.ofInputStream()), executor) .map(JdkClientHttpResponse::new); } diff --git a/today-web/src/main/java/cn/taketoday/http/client/ReactorClientHttpRequest.java b/today-web/src/main/java/cn/taketoday/http/client/ReactorClientHttpRequest.java index a2abb366d..0af6decdc 100755 --- a/today-web/src/main/java/cn/taketoday/http/client/ReactorClientHttpRequest.java +++ b/today-web/src/main/java/cn/taketoday/http/client/ReactorClientHttpRequest.java @@ -146,13 +146,13 @@ static IOException convertException(RuntimeException ex) { } @Override - protected Future asyncInternal(HttpHeaders headers, @Nullable Body body) { + protected Future asyncInternal(HttpHeaders headers, @Nullable Body body, @Nullable Executor executor) { HttpClient.RequestSender requestSender = httpClient .request(io.netty.handler.codec.http.HttpMethod.valueOf(method.name())); requestSender = uri.isAbsolute() ? requestSender.uri(uri) : requestSender.uri(uri.toString()); - Promise promise = Future.forPromise(); + Promise promise = Future.forPromise(executor); requestSender.send((reactorRequest, nettyOutbound) -> send(headers, body, reactorRequest, nettyOutbound)) .responseConnection((reactorResponse, connection) -> Mono.just(new ReactorClientHttpResponse(reactorResponse, connection, readTimeout))) .next() diff --git a/today-web/src/main/java/cn/taketoday/web/client/DefaultRestClient.java b/today-web/src/main/java/cn/taketoday/web/client/DefaultRestClient.java index f37f2f411..9c45d99e8 100755 --- a/today-web/src/main/java/cn/taketoday/web/client/DefaultRestClient.java +++ b/today-web/src/main/java/cn/taketoday/web/client/DefaultRestClient.java @@ -616,14 +616,9 @@ public ResponseEntity toEntity(ParameterizedTypeReference bodyType) { private ResponseEntity toEntityInternal(Type bodyType, Class bodyClass) { T body = readBody(bodyType, bodyClass); - try { - return ResponseEntity.status(this.clientResponse.getStatusCode()) - .headers(this.clientResponse.getHeaders()) - .body(body); - } - catch (IOException ex) { - throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex); - } + return ResponseEntity.status(this.clientResponse.getStatusCode()) + .headers(this.clientResponse.getHeaders()) + .body(body); } @Override @@ -637,9 +632,6 @@ public ResponseEntity toBodilessEntity() { catch (UncheckedIOException ex) { throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex.getCause()); } - catch (IOException ex) { - throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex); - } } @Nullable @@ -699,12 +691,17 @@ public HttpHeaders getHeaders() { } @Override - public HttpStatusCode getStatusCode() throws IOException { + public HttpStatusCode getStatusCode() { return this.delegate.getStatusCode(); } @Override - public String getStatusText() throws IOException { + public int getRawStatusCode() { + return delegate.getRawStatusCode(); + } + + @Override + public String getStatusText() { return this.delegate.getStatusText(); } diff --git a/today-web/src/main/java/cn/taketoday/web/client/RestTemplate.java b/today-web/src/main/java/cn/taketoday/web/client/RestTemplate.java index 2657d06b9..b556fecfe 100755 --- a/today-web/src/main/java/cn/taketoday/web/client/RestTemplate.java +++ b/today-web/src/main/java/cn/taketoday/web/client/RestTemplate.java @@ -32,7 +32,6 @@ import cn.taketoday.http.HttpEntity; import cn.taketoday.http.HttpHeaders; import cn.taketoday.http.HttpMethod; -import cn.taketoday.http.HttpStatusCode; import cn.taketoday.http.MediaType; import cn.taketoday.http.RequestEntity; import cn.taketoday.http.ResponseEntity; @@ -702,8 +701,8 @@ public T execute(URI url, HttpMethod method, @Nullable RequestCallback reque * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ @Nullable - protected T doExecute(URI url, @Nullable HttpMethod method, - @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { + protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, + @Nullable ResponseExtractor responseExtractor) throws RestClientException { Assert.notNull(url, "URI is required"); Assert.notNull(method, "HttpMethod is required"); @@ -746,13 +745,7 @@ protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse res ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { - try { - HttpStatusCode status = response.getStatusCode(); - logger.debug("{} Response {}", url, status); - } - catch (IOException ex) { - logger.debug("Failed to get response status code", ex); - } + logger.debug("{} Response {}", url, response.getStatusCode()); } if (hasError) { errorHandler.handleError(url, method, response); @@ -965,6 +958,7 @@ private static void copyHttpHeaders(HttpHeaders httpHeaders, HttpHeaders request * Response extractor for {@link HttpEntity}. */ private class ResponseEntityResponseExtractor implements ResponseExtractor> { + @Nullable private final HttpMessageConverterExtractor delegate; diff --git a/today-web/src/test/java/cn/taketoday/http/client/AbstractHttpRequestFactoryTests.java b/today-web/src/test/java/cn/taketoday/http/client/AbstractHttpRequestFactoryTests.java index 976486f2a..51db77507 100755 --- a/today-web/src/test/java/cn/taketoday/http/client/AbstractHttpRequestFactoryTests.java +++ b/today-web/src/test/java/cn/taketoday/http/client/AbstractHttpRequestFactoryTests.java @@ -1,8 +1,5 @@ /* - * Original Author -> Harry Yang (taketoday@foxmail.com) https://taketoday.cn - * Copyright © Harry Yang & 2017 - 2023 All Rights Reserved. - * - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER + * Copyright 2017 - 2024 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +12,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see [http://www.gnu.org/licenses/] + * along with this program. If not, see [https://www.gnu.org/licenses/] */ package cn.taketoday.http.client; @@ -26,6 +23,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Arrays; import java.util.Locale; @@ -183,4 +181,15 @@ void queryParameters() throws Exception { } } + @Test + void async() throws Exception { + URI uri = new URI(baseUrl + "/params?param1=value¶m2=value1¶m2=value2"); + ClientHttpRequest request = factory.createRequest(uri, HttpMethod.GET); + + var future = request.async(); + assertThat(future).succeedsWithin(Duration.ofSeconds(1)) + .extracting(ClientHttpResponse::getStatusCode) + .as("Invalid status code").isEqualTo(HttpStatus.OK); + } + } diff --git a/today-web/src/test/java/cn/taketoday/http/client/AbstractMockWebServerTests.java b/today-web/src/test/java/cn/taketoday/http/client/AbstractMockWebServerTests.java index 7bf921316..b001a2667 100755 --- a/today-web/src/test/java/cn/taketoday/http/client/AbstractMockWebServerTests.java +++ b/today-web/src/test/java/cn/taketoday/http/client/AbstractMockWebServerTests.java @@ -20,9 +20,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import java.util.Collections; - -import cn.taketoday.http.MediaType; import cn.taketoday.util.StringUtils; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; @@ -42,9 +39,6 @@ public abstract class AbstractMockWebServerTests { protected String baseUrl; - protected static final MediaType textContentType = - new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8")); - @BeforeEach public void setUp() throws Exception { this.server = new MockWebServer(); diff --git a/today-web/src/testFixtures/java/cn/taketoday/web/testfixture/http/MockClientHttpRequest.java b/today-web/src/testFixtures/java/cn/taketoday/web/testfixture/http/MockClientHttpRequest.java index 8d949cd8c..b6e256546 100644 --- a/today-web/src/testFixtures/java/cn/taketoday/web/testfixture/http/MockClientHttpRequest.java +++ b/today-web/src/testFixtures/java/cn/taketoday/web/testfixture/http/MockClientHttpRequest.java @@ -21,6 +21,7 @@ import java.net.URI; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import cn.taketoday.http.HttpMethod; import cn.taketoday.http.MockHttpOutputMessage; @@ -145,8 +146,8 @@ public final ClientHttpResponse execute() throws IOException { } @Override - public Future async() { - return Future.run(this::execute); + public Future async(@Nullable Executor executor) { + return Future.run(this::execute, executor); } /**