Skip to content

Commit 990a340

Browse files
committed
Upgrade RestTemplate to HttpClient 5
This commit upgrades the HttpComponentClientHttpRequestFactory and related types from HttpClient version 4.5 to 5. Closes gh-28925
1 parent cc3616d commit 990a340

File tree

10 files changed

+189
-218
lines changed

10 files changed

+189
-218
lines changed

framework-platform/framework-platform.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ dependencies {
9494
api("org.apache.derby:derbyclient:10.14.2.0")
9595
api("org.apache.httpcomponents.client5:httpclient5:5.1.3")
9696
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3")
97-
api("org.apache.httpcomponents:httpclient:4.5.13")
9897
api("org.apache.poi:poi-ooxml:5.2.2")
9998
api("org.apache.tomcat.embed:tomcat-embed-core:10.0.23")
10099
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.0.23")

spring-web/spring-web.gradle

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ dependencies {
4040
optional("org.eclipse.jetty:jetty-reactive-httpclient")
4141
optional('org.apache.httpcomponents.client5:httpclient5')
4242
optional('org.apache.httpcomponents.core5:httpcore5-reactive')
43-
optional("org.apache.httpcomponents:httpclient") {
44-
exclude group: "commons-logging", module: "commons-logging"
45-
}
4643
optional("com.squareup.okhttp3:okhttp")
4744
optional("com.fasterxml.woodstox:woodstox-core")
4845
optional("com.fasterxml:aalto-xml")

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,18 +18,20 @@
1818

1919
import java.io.IOException;
2020
import java.net.URI;
21+
import java.net.URISyntaxException;
2122

22-
import org.apache.http.HttpEntity;
23-
import org.apache.http.HttpEntityEnclosingRequest;
24-
import org.apache.http.HttpResponse;
25-
import org.apache.http.client.HttpClient;
26-
import org.apache.http.client.methods.HttpUriRequest;
27-
import org.apache.http.entity.ByteArrayEntity;
28-
import org.apache.http.protocol.HTTP;
29-
import org.apache.http.protocol.HttpContext;
23+
import org.apache.hc.client5.http.classic.HttpClient;
24+
import org.apache.hc.core5.http.ClassicHttpRequest;
25+
import org.apache.hc.core5.http.ClassicHttpResponse;
26+
import org.apache.hc.core5.http.ContentType;
27+
import org.apache.hc.core5.http.HttpEntity;
28+
import org.apache.hc.core5.http.HttpResponse;
29+
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
30+
import org.apache.hc.core5.http.protocol.HttpContext;
3031

3132
import org.springframework.http.HttpHeaders;
3233
import org.springframework.http.HttpMethod;
34+
import org.springframework.util.Assert;
3335
import org.springframework.util.StringUtils;
3436

3537
/**
@@ -48,12 +50,12 @@ final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpR
4850

4951
private final HttpClient httpClient;
5052

51-
private final HttpUriRequest httpRequest;
53+
private final ClassicHttpRequest httpRequest;
5254

5355
private final HttpContext httpContext;
5456

5557

56-
HttpComponentsClientHttpRequest(HttpClient client, HttpUriRequest request, HttpContext context) {
58+
HttpComponentsClientHttpRequest(HttpClient client, ClassicHttpRequest request, HttpContext context) {
5759
this.httpClient = client;
5860
this.httpRequest = request;
5961
this.httpContext = context;
@@ -73,7 +75,12 @@ public String getMethodValue() {
7375

7476
@Override
7577
public URI getURI() {
76-
return this.httpRequest.getURI();
78+
try {
79+
return this.httpRequest.getUri();
80+
}
81+
catch (URISyntaxException ex) {
82+
throw new IllegalStateException(ex.getMessage(), ex);
83+
}
7784
}
7885

7986
HttpContext getHttpContext() {
@@ -85,28 +92,28 @@ HttpContext getHttpContext() {
8592
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
8693
addHeaders(this.httpRequest, headers);
8794

88-
if (this.httpRequest instanceof HttpEntityEnclosingRequest entityEnclosingRequest) {
89-
HttpEntity requestEntity = new ByteArrayEntity(bufferedOutput);
90-
entityEnclosingRequest.setEntity(requestEntity);
91-
}
95+
ContentType contentType = ContentType.parse(headers.getFirst(HttpHeaders.CONTENT_TYPE));
96+
HttpEntity requestEntity = new ByteArrayEntity(bufferedOutput, contentType);
97+
this.httpRequest.setEntity(requestEntity);
9298
HttpResponse httpResponse = this.httpClient.execute(this.httpRequest, this.httpContext);
93-
return new HttpComponentsClientHttpResponse(httpResponse);
99+
Assert.isInstanceOf(ClassicHttpResponse.class, httpResponse,
100+
"HttpResponse not an instance of ClassicHttpResponse");
101+
return new HttpComponentsClientHttpResponse((ClassicHttpResponse) httpResponse);
94102
}
95103

96-
97104
/**
98105
* Add the given headers to the given HTTP request.
99106
* @param httpRequest the request to add the headers to
100107
* @param headers the headers to add
101108
*/
102-
static void addHeaders(HttpUriRequest httpRequest, HttpHeaders headers) {
109+
static void addHeaders(ClassicHttpRequest httpRequest, HttpHeaders headers) {
103110
headers.forEach((headerName, headerValues) -> {
104111
if (HttpHeaders.COOKIE.equalsIgnoreCase(headerName)) { // RFC 6265
105112
String headerValue = StringUtils.collectionToDelimitedString(headerValues, "; ");
106113
httpRequest.addHeader(headerName, headerValue);
107114
}
108-
else if (!HTTP.CONTENT_LEN.equalsIgnoreCase(headerName) &&
109-
!HTTP.TRANSFER_ENCODING.equalsIgnoreCase(headerName)) {
115+
else if (!HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(headerName) &&
116+
!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(headerName)) {
110117
for (String headerValue : headerValues) {
111118
httpRequest.addHeader(headerName, headerValue);
112119
}

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

Lines changed: 60 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,29 @@
1919
import java.io.Closeable;
2020
import java.io.IOException;
2121
import java.net.URI;
22+
import java.util.concurrent.TimeUnit;
2223
import java.util.function.BiFunction;
2324

24-
import org.apache.http.client.HttpClient;
25-
import org.apache.http.client.config.RequestConfig;
26-
import org.apache.http.client.methods.Configurable;
27-
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
28-
import org.apache.http.client.methods.HttpGet;
29-
import org.apache.http.client.methods.HttpHead;
30-
import org.apache.http.client.methods.HttpOptions;
31-
import org.apache.http.client.methods.HttpPatch;
32-
import org.apache.http.client.methods.HttpPost;
33-
import org.apache.http.client.methods.HttpPut;
34-
import org.apache.http.client.methods.HttpTrace;
35-
import org.apache.http.client.methods.HttpUriRequest;
36-
import org.apache.http.client.protocol.HttpClientContext;
37-
import org.apache.http.impl.client.HttpClients;
38-
import org.apache.http.protocol.HttpContext;
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import org.apache.hc.client5.http.classic.HttpClient;
28+
import org.apache.hc.client5.http.classic.methods.HttpDelete;
29+
import org.apache.hc.client5.http.classic.methods.HttpGet;
30+
import org.apache.hc.client5.http.classic.methods.HttpHead;
31+
import org.apache.hc.client5.http.classic.methods.HttpOptions;
32+
import org.apache.hc.client5.http.classic.methods.HttpPatch;
33+
import org.apache.hc.client5.http.classic.methods.HttpPost;
34+
import org.apache.hc.client5.http.classic.methods.HttpPut;
35+
import org.apache.hc.client5.http.classic.methods.HttpTrace;
36+
import org.apache.hc.client5.http.config.Configurable;
37+
import org.apache.hc.client5.http.config.RequestConfig;
38+
import org.apache.hc.client5.http.impl.classic.HttpClients;
39+
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
40+
import org.apache.hc.client5.http.protocol.HttpClientContext;
41+
import org.apache.hc.core5.http.ClassicHttpRequest;
42+
import org.apache.hc.core5.http.io.SocketConfig;
43+
import org.apache.hc.core5.http.protocol.HttpContext;
44+
import org.apache.hc.core5.util.Timeout;
3945

4046
import org.springframework.beans.factory.DisposableBean;
4147
import org.springframework.http.HttpMethod;
@@ -60,16 +66,19 @@
6066
*/
6167
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
6268

63-
private HttpClient httpClient;
69+
private static final Log logger = LogFactory.getLog(HttpComponentsClientHttpRequestFactory.class);
6470

65-
@Nullable
66-
private RequestConfig requestConfig;
71+
72+
private HttpClient httpClient;
6773

6874
private boolean bufferRequestBody = true;
6975

7076
@Nullable
7177
private BiFunction<HttpMethod, URI, HttpContext> httpContextFactory;
7278

79+
private int connectTimeout = -1;
80+
81+
private int connectionRequestTimeout = -1;
7382

7483
/**
7584
* Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
@@ -113,15 +122,15 @@ public HttpClient getHttpClient() {
113122
* {@link RequestConfig} instance on a custom {@link HttpClient}.
114123
* <p>This options does not affect connection timeouts for SSL
115124
* handshakes or CONNECT requests; for that, it is required to
116-
* use the {@link org.apache.http.config.SocketConfig} on the
125+
* use the {@link SocketConfig} on the
117126
* {@link HttpClient} itself.
118-
* @param timeout the timeout value in milliseconds
127+
* @param connectTimeout the timeout value in milliseconds
119128
* @see RequestConfig#getConnectTimeout()
120-
* @see org.apache.http.config.SocketConfig#getSoTimeout
129+
* @see SocketConfig#getSoTimeout
121130
*/
122-
public void setConnectTimeout(int timeout) {
123-
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
124-
this.requestConfig = requestConfigBuilder().setConnectTimeout(timeout).build();
131+
public void setConnectTimeout(int connectTimeout) {
132+
Assert.isTrue(connectTimeout >= 0, "Timeout must be a non-negative value");
133+
this.connectTimeout = connectTimeout;
125134
}
126135

127136
/**
@@ -134,21 +143,24 @@ public void setConnectTimeout(int timeout) {
134143
* @see RequestConfig#getConnectionRequestTimeout()
135144
*/
136145
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
137-
this.requestConfig = requestConfigBuilder()
138-
.setConnectionRequestTimeout(connectionRequestTimeout).build();
146+
Assert.isTrue(connectionRequestTimeout >= 0, "Timeout must be a non-negative value");
147+
this.connectionRequestTimeout = connectionRequestTimeout;
139148
}
140149

141150
/**
142-
* Set the socket read timeout for the underlying {@link RequestConfig}.
143-
* A timeout value of 0 specifies an infinite timeout.
144-
* <p>Additional properties can be configured by specifying a
145-
* {@link RequestConfig} instance on a custom {@link HttpClient}.
146-
* @param timeout the timeout value in milliseconds
147-
* @see RequestConfig#getSocketTimeout()
151+
* As of version 6.0, setting this property has no effect.
152+
*
153+
* <p/>To change the socket read timeout, use {@link SocketConfig.Builder#setSoTimeout(Timeout)},
154+
* supply the resulting {@link SocketConfig} to
155+
* {@link org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder#setDefaultSocketConfig(SocketConfig)},
156+
* use the resulting connection manager for
157+
* {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder#setConnectionManager(HttpClientConnectionManager)},
158+
* and supply the built {@link HttpClient} to {@link #HttpComponentsClientHttpRequestFactory(HttpClient)}.
159+
* @deprecated as of 6.0, in favor of {@link SocketConfig.Builder#setSoTimeout(Timeout)}, see above.
148160
*/
161+
@Deprecated(since = "6.0", forRemoval = true)
149162
public void setReadTimeout(int timeout) {
150-
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
151-
this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
163+
logger.warn("HttpComponentsClientHttpRequestFactory.setReadTimeout has no effect");
152164
}
153165

154166
/**
@@ -179,7 +191,7 @@ public void setHttpContextFactory(BiFunction<HttpMethod, URI, HttpContext> httpC
179191
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
180192
HttpClient client = getHttpClient();
181193

182-
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
194+
ClassicHttpRequest httpRequest = createHttpUriRequest(httpMethod, uri);
183195
postProcessHttpRequest(httpRequest);
184196
HttpContext context = createHttpContext(httpMethod, uri);
185197
if (context == null) {
@@ -210,14 +222,6 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO
210222
}
211223

212224

213-
/**
214-
* Return a builder for modifying the factory-level {@link RequestConfig}.
215-
* @since 4.2
216-
*/
217-
private RequestConfig.Builder requestConfigBuilder() {
218-
return (this.requestConfig != null ? RequestConfig.copy(this.requestConfig) : RequestConfig.custom());
219-
}
220-
221225
/**
222226
* Create a default {@link RequestConfig} to use with the given client.
223227
* Can return {@code null} to indicate that no custom request config should
@@ -231,37 +235,31 @@ private RequestConfig.Builder requestConfigBuilder() {
231235
*/
232236
@Nullable
233237
protected RequestConfig createRequestConfig(Object client) {
234-
if (client instanceof Configurable) {
235-
RequestConfig clientRequestConfig = ((Configurable) client).getConfig();
238+
if (client instanceof Configurable configurableClient) {
239+
RequestConfig clientRequestConfig = configurableClient.getConfig();
236240
return mergeRequestConfig(clientRequestConfig);
237241
}
238-
return this.requestConfig;
242+
return mergeRequestConfig(RequestConfig.DEFAULT);
239243
}
240244

241245
/**
242246
* Merge the given {@link HttpClient}-level {@link RequestConfig} with
243-
* the factory-level {@link RequestConfig}, if necessary.
247+
* the factory-level configuration, if necessary.
244248
* @param clientConfig the config held by the current
245249
* @return the merged request config
246250
* @since 4.2
247251
*/
248252
protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
249-
if (this.requestConfig == null) { // nothing to merge
253+
if (this.connectTimeout == -1 && this.connectionRequestTimeout == -1) { // nothing to merge
250254
return clientConfig;
251255
}
252256

253257
RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
254-
int connectTimeout = this.requestConfig.getConnectTimeout();
255-
if (connectTimeout >= 0) {
256-
builder.setConnectTimeout(connectTimeout);
257-
}
258-
int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
259-
if (connectionRequestTimeout >= 0) {
260-
builder.setConnectionRequestTimeout(connectionRequestTimeout);
258+
if (this.connectTimeout >= 0) {
259+
builder.setConnectTimeout(this.connectTimeout, TimeUnit.MILLISECONDS);
261260
}
262-
int socketTimeout = this.requestConfig.getSocketTimeout();
263-
if (socketTimeout >= 0) {
264-
builder.setSocketTimeout(socketTimeout);
261+
if (this.connectionRequestTimeout >= 0) {
262+
builder.setConnectionRequestTimeout(this.connectionRequestTimeout, TimeUnit.MILLISECONDS);
265263
}
266264
return builder.build();
267265
}
@@ -272,7 +270,7 @@ protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
272270
* @param uri the URI
273271
* @return the Commons HttpMethodBase object
274272
*/
275-
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
273+
protected ClassicHttpRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
276274
if (HttpMethod.GET.equals(httpMethod)) {
277275
return new HttpGet(uri);
278276
}
@@ -301,12 +299,12 @@ else if (HttpMethod.TRACE.equals(httpMethod)) {
301299
}
302300

303301
/**
304-
* Template method that allows for manipulating the {@link HttpUriRequest} before it is
302+
* Template method that allows for manipulating the {@link ClassicHttpRequest} before it is
305303
* returned as part of a {@link HttpComponentsClientHttpRequest}.
306304
* <p>The default implementation is empty.
307305
* @param request the request to process
308306
*/
309-
protected void postProcessHttpRequest(HttpUriRequest request) {
307+
protected void postProcessHttpRequest(ClassicHttpRequest request) {
310308
}
311309

312310
/**
@@ -324,7 +322,7 @@ protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
324322

325323
/**
326324
* Shutdown hook that closes the underlying
327-
* {@link org.apache.http.conn.HttpClientConnectionManager ClientConnectionManager}'s
325+
* {@link HttpClientConnectionManager ClientConnectionManager}'s
328326
* connection pool, if any.
329327
*/
330328
@Override
@@ -340,25 +338,4 @@ public void close() throws IOException {
340338
}
341339
}
342340

343-
/**
344-
* An alternative to {@link org.apache.http.client.methods.HttpDelete} that
345-
* extends {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase}
346-
* rather than {@link org.apache.http.client.methods.HttpRequestBase} and
347-
* hence allows HTTP delete with a request body. For use with the RestTemplate
348-
* exchange methods which allow the combination of HTTP DELETE with an entity.
349-
* @since 4.1.2
350-
*/
351-
private static class HttpDelete extends HttpEntityEnclosingRequestBase {
352-
353-
public HttpDelete(URI uri) {
354-
super();
355-
setURI(uri);
356-
}
357-
358-
@Override
359-
public String getMethod() {
360-
return "DELETE";
361-
}
362-
}
363-
364341
}

0 commit comments

Comments
 (0)