Skip to content

Commit 042346a

Browse files
committed
Harmonize Reactor client class names within the http.client package
Closes gh-33382
1 parent e9e5fee commit 042346a

File tree

7 files changed

+276
-217
lines changed

7 files changed

+276
-217
lines changed

spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequest.java renamed to spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequest.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939

4040
/**
4141
* {@link ClientHttpRequest} implementation for the Reactor-Netty HTTP client.
42-
* Created via the {@link ReactorNettyClientRequestFactory}.
42+
* Created via the {@link ReactorClientHttpRequestFactory}.
4343
*
4444
* @author Arjen Poutsma
4545
* @author Juergen Hoeller
4646
* @since 6.1
4747
*/
48-
final class ReactorNettyClientRequest extends AbstractStreamingClientHttpRequest {
48+
final class ReactorClientHttpRequest extends AbstractStreamingClientHttpRequest {
4949

5050
private final HttpClient httpClient;
5151

@@ -58,8 +58,8 @@ final class ReactorNettyClientRequest extends AbstractStreamingClientHttpRequest
5858
private final Duration readTimeout;
5959

6060

61-
public ReactorNettyClientRequest(HttpClient httpClient, URI uri, HttpMethod method,
62-
Duration exchangeTimeout, Duration readTimeout) {
61+
public ReactorClientHttpRequest(HttpClient httpClient, URI uri, HttpMethod method,
62+
Duration exchangeTimeout, Duration readTimeout) {
6363

6464
this.httpClient = httpClient;
6565
this.method = method;
@@ -88,10 +88,10 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
8888
requestSender = (this.uri.isAbsolute() ? requestSender.uri(this.uri) : requestSender.uri(this.uri.toString()));
8989

9090
try {
91-
ReactorNettyClientResponse result = requestSender.send((reactorRequest, nettyOutbound) ->
91+
ReactorClientHttpResponse result = requestSender.send((reactorRequest, nettyOutbound) ->
9292
send(headers, body, reactorRequest, nettyOutbound))
9393
.responseConnection((reactorResponse, connection) ->
94-
Mono.just(new ReactorNettyClientResponse(reactorResponse, connection, this.readTimeout)))
94+
Mono.just(new ReactorClientHttpResponse(reactorResponse, connection, this.readTimeout)))
9595
.next()
9696
.block(this.exchangeTimeout);
9797

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
import java.time.Duration;
22+
import java.util.function.Function;
23+
24+
import io.netty.channel.ChannelOption;
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import reactor.netty.http.client.HttpClient;
28+
import reactor.netty.resources.ConnectionProvider;
29+
import reactor.netty.resources.LoopResources;
30+
31+
import org.springframework.context.SmartLifecycle;
32+
import org.springframework.http.HttpMethod;
33+
import org.springframework.lang.Nullable;
34+
import org.springframework.util.Assert;
35+
36+
/**
37+
* Reactor-Netty implementation of {@link ClientHttpRequestFactory}.
38+
*
39+
* <p>This class implements {@link SmartLifecycle} and can be optionally declared
40+
* as a Spring-managed bean in order to support JVM Checkpoint Restore.
41+
*
42+
* @author Arjen Poutsma
43+
* @author Juergen Hoeller
44+
* @author Sebastien Deleuze
45+
* @since 6.2
46+
*/
47+
public class ReactorClientHttpRequestFactory implements ClientHttpRequestFactory, SmartLifecycle {
48+
49+
private static final Log logger = LogFactory.getLog(ReactorClientHttpRequestFactory.class);
50+
51+
private static final Function<HttpClient, HttpClient> defaultInitializer = client -> client.compress(true);
52+
53+
54+
@Nullable
55+
private final ReactorResourceFactory resourceFactory;
56+
57+
@Nullable
58+
private final Function<HttpClient, HttpClient> mapper;
59+
60+
@Nullable
61+
private Integer connectTimeout;
62+
63+
private Duration readTimeout = Duration.ofSeconds(10);
64+
65+
private Duration exchangeTimeout = Duration.ofSeconds(5);
66+
67+
@Nullable
68+
private volatile HttpClient httpClient;
69+
70+
private final Object lifecycleMonitor = new Object();
71+
72+
73+
/**
74+
* Create a new instance of the {@code ReactorClientHttpRequestFactory}
75+
* with a default {@link HttpClient} that has compression enabled.
76+
*/
77+
public ReactorClientHttpRequestFactory() {
78+
this.httpClient = defaultInitializer.apply(HttpClient.create());
79+
this.resourceFactory = null;
80+
this.mapper = null;
81+
}
82+
83+
/**
84+
* Create a new instance of the {@code ReactorClientHttpRequestFactory}
85+
* based on the given {@link HttpClient}.
86+
* @param httpClient the client to base on
87+
*/
88+
public ReactorClientHttpRequestFactory(HttpClient httpClient) {
89+
Assert.notNull(httpClient, "HttpClient must not be null");
90+
this.httpClient = httpClient;
91+
this.resourceFactory = null;
92+
this.mapper = null;
93+
}
94+
95+
/**
96+
* Constructor with externally managed Reactor Netty resources, including
97+
* {@link LoopResources} for event loop threads, and {@link ConnectionProvider}
98+
* for the connection pool.
99+
* <p>This constructor should be used only when you don't want the client
100+
* to participate in the Reactor Netty global resources. By default the
101+
* client participates in the Reactor Netty global resources held in
102+
* {@link reactor.netty.http.HttpResources}, which is recommended since
103+
* fixed, shared resources are favored for event loop concurrency. However,
104+
* consider declaring a {@link ReactorResourceFactory} bean with
105+
* {@code globalResources=true} in order to ensure the Reactor Netty global
106+
* resources are shut down when the Spring ApplicationContext is stopped or closed
107+
* and restarted properly when the Spring ApplicationContext is
108+
* (with JVM Checkpoint Restore for example).
109+
* @param resourceFactory the resource factory to obtain the resources from
110+
* @param mapper a mapper for further initialization of the created client
111+
*/
112+
public ReactorClientHttpRequestFactory(ReactorResourceFactory resourceFactory, Function<HttpClient, HttpClient> mapper) {
113+
this.resourceFactory = resourceFactory;
114+
this.mapper = mapper;
115+
if (resourceFactory.isRunning()) {
116+
this.httpClient = createHttpClient(resourceFactory, mapper);
117+
}
118+
}
119+
120+
121+
/**
122+
* Set the underlying connect timeout in milliseconds.
123+
* A value of 0 specifies an infinite timeout.
124+
* <p>Default is 30 seconds.
125+
* @see HttpClient#option(ChannelOption, Object)
126+
* @see ChannelOption#CONNECT_TIMEOUT_MILLIS
127+
*/
128+
public void setConnectTimeout(int connectTimeout) {
129+
Assert.isTrue(connectTimeout >= 0, "Timeout must be a non-negative value");
130+
this.connectTimeout = connectTimeout;
131+
HttpClient httpClient = this.httpClient;
132+
if (httpClient != null) {
133+
this.httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.connectTimeout);
134+
}
135+
}
136+
137+
/**
138+
* Set the underlying connect timeout in milliseconds.
139+
* A value of 0 specifies an infinite timeout.
140+
* <p>Default is 30 seconds.
141+
* @see HttpClient#option(ChannelOption, Object)
142+
* @see ChannelOption#CONNECT_TIMEOUT_MILLIS
143+
*/
144+
public void setConnectTimeout(Duration connectTimeout) {
145+
Assert.notNull(connectTimeout, "ConnectTimeout must not be null");
146+
setConnectTimeout((int) connectTimeout.toMillis());
147+
}
148+
149+
/**
150+
* Set the underlying read timeout in milliseconds.
151+
* <p>Default is 10 seconds.
152+
*/
153+
public void setReadTimeout(long readTimeout) {
154+
Assert.isTrue(readTimeout > 0, "Timeout must be a positive value");
155+
this.readTimeout = Duration.ofMillis(readTimeout);
156+
}
157+
158+
/**
159+
* Set the underlying read timeout as {@code Duration}.
160+
* <p>Default is 10 seconds.
161+
*/
162+
public void setReadTimeout(Duration readTimeout) {
163+
Assert.notNull(readTimeout, "ReadTimeout must not be null");
164+
Assert.isTrue(!readTimeout.isNegative(), "Timeout must be a non-negative value");
165+
this.readTimeout = readTimeout;
166+
}
167+
168+
/**
169+
* Set the timeout for the HTTP exchange in milliseconds.
170+
* <p>Default is 5 seconds.
171+
*/
172+
public void setExchangeTimeout(long exchangeTimeout) {
173+
Assert.isTrue(exchangeTimeout > 0, "Timeout must be a positive value");
174+
this.exchangeTimeout = Duration.ofMillis(exchangeTimeout);
175+
}
176+
177+
/**
178+
* Set the timeout for the HTTP exchange.
179+
* <p>Default is 5 seconds.
180+
*/
181+
public void setExchangeTimeout(Duration exchangeTimeout) {
182+
Assert.notNull(exchangeTimeout, "ExchangeTimeout must not be null");
183+
Assert.isTrue(!exchangeTimeout.isNegative(), "Timeout must be a non-negative value");
184+
this.exchangeTimeout = exchangeTimeout;
185+
}
186+
187+
private HttpClient createHttpClient(ReactorResourceFactory factory, Function<HttpClient, HttpClient> mapper) {
188+
HttpClient httpClient = defaultInitializer.andThen(mapper)
189+
.apply(HttpClient.create(factory.getConnectionProvider()));
190+
httpClient = httpClient.runOn(factory.getLoopResources());
191+
if (this.connectTimeout != null) {
192+
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.connectTimeout);
193+
}
194+
return httpClient;
195+
}
196+
197+
198+
@Override
199+
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
200+
HttpClient httpClient = this.httpClient;
201+
if (httpClient == null) {
202+
Assert.state(this.resourceFactory != null && this.mapper != null, "Illegal configuration");
203+
httpClient = createHttpClient(this.resourceFactory, this.mapper);
204+
}
205+
return new ReactorClientHttpRequest(httpClient, uri, httpMethod, this.exchangeTimeout, this.readTimeout);
206+
}
207+
208+
209+
@Override
210+
public void start() {
211+
if (this.resourceFactory != null && this.mapper != null) {
212+
synchronized (this.lifecycleMonitor) {
213+
if (this.httpClient == null) {
214+
this.httpClient = createHttpClient(this.resourceFactory, this.mapper);
215+
}
216+
}
217+
}
218+
else {
219+
logger.warn("Restarting a ReactorClientHttpRequestFactory bean is only supported " +
220+
"with externally managed Reactor Netty resources");
221+
}
222+
}
223+
224+
@Override
225+
public void stop() {
226+
if (this.resourceFactory != null && this.mapper != null) {
227+
synchronized (this.lifecycleMonitor) {
228+
this.httpClient = null;
229+
}
230+
}
231+
}
232+
233+
@Override
234+
public boolean isRunning() {
235+
return (this.httpClient != null);
236+
}
237+
238+
@Override
239+
public int getPhase() {
240+
// Start after ReactorResourceFactory
241+
return 1;
242+
}
243+
244+
}

spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientResponse.java renamed to spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpResponse.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
* @author Juergen Hoeller
3737
* @since 6.1
3838
*/
39-
final class ReactorNettyClientResponse implements ClientHttpResponse {
39+
final class ReactorClientHttpResponse implements ClientHttpResponse {
4040

4141
private final HttpClientResponse response;
4242

@@ -50,7 +50,7 @@ final class ReactorNettyClientResponse implements ClientHttpResponse {
5050
private volatile InputStream body;
5151

5252

53-
public ReactorNettyClientResponse(HttpClientResponse response, Connection connection, Duration readTimeout) {
53+
public ReactorClientHttpResponse(HttpClientResponse response, Connection connection, Duration readTimeout) {
5454
this.response = response;
5555
this.connection = connection;
5656
this.readTimeout = readTimeout;
@@ -84,7 +84,7 @@ public InputStream getBody() throws IOException {
8484
body = this.connection.inbound().receive().aggregate().asInputStream().block(this.readTimeout);
8585
}
8686
catch (RuntimeException ex) {
87-
throw ReactorNettyClientRequest.convertException(ex);
87+
throw ReactorClientHttpRequest.convertException(ex);
8888
}
8989

9090
if (body == null) {

0 commit comments

Comments
 (0)