Skip to content

Commit 05daff5

Browse files
cachescrubbergregturn
authored andcommitted
Introduce HttpComponents HttpClient 5 support.
Resolves #1164. Original Pull Request: #1356. polish polish polish
1 parent 6edbdeb commit 05daff5

File tree

6 files changed

+824
-0
lines changed

6 files changed

+824
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
<ehcache.version>2.10.9.2</ehcache.version>
9797
<greenmail.version>2.0.0-alpha-2</greenmail.version>
9898
<httpclient.version>4.5.3</httpclient.version>
99+
<httpclient5.version>5.2.1</httpclient5.version>
99100
<jakarta-annotation.version>2.1.1</jakarta-annotation.version>
100101
<jakarta-jms.version>3.1.0</jakarta-jms.version>
101102
<jakarta-mail-api.version>2.1.0</jakarta-mail-api.version>

spring-ws-core/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,24 @@
160160
</exclusion>
161161
</exclusions>
162162
</dependency>
163+
<dependency>
164+
<groupId>org.apache.httpcomponents.client5</groupId>
165+
<artifactId>httpclient5</artifactId>
166+
<version>${httpclient5.version}</version>
167+
<optional>true</optional>
168+
</dependency>
169+
<dependency>
170+
<groupId>org.apache.httpcomponents</groupId>
171+
<artifactId>httpclient</artifactId>
172+
<version>${httpclient.version}</version>
173+
<optional>true</optional>
174+
<exclusions>
175+
<exclusion>
176+
<groupId>commons-logging</groupId>
177+
<artifactId>commons-logging</artifactId>
178+
</exclusion>
179+
</exclusions>
180+
</dependency>
163181

164182
<dependency>
165183
<groupId>org.eclipse.jetty</groupId>
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Copyright 2005-2023 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+
* http://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.ws.transport.http;
18+
19+
import java.net.URI;
20+
import java.net.URISyntaxException;
21+
import java.util.Map;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.apache.hc.client5.http.HttpRoute;
25+
import org.apache.hc.client5.http.auth.AuthScope;
26+
import org.apache.hc.client5.http.auth.Credentials;
27+
import org.apache.hc.client5.http.config.RequestConfig;
28+
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
29+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
30+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
31+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
32+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
33+
import org.apache.hc.core5.http.HttpHost;
34+
35+
import org.springframework.beans.factory.FactoryBean;
36+
37+
/**
38+
* {@code FactoryBean} to set up a <a href="http://hc.apache.org/httpcomponents-client">Apache
39+
* CloseableHttpClient</a>
40+
*
41+
* @author Lars Uffmann
42+
* @since 4.0.5
43+
*/
44+
public class HttpComponents5ClientFactory implements FactoryBean<CloseableHttpClient> {
45+
private static final int DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS = (60 * 1000);
46+
47+
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS;
48+
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
49+
50+
private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS;
51+
52+
private int maxTotalConnections = -1;
53+
private AuthScope authScope = null;
54+
55+
private Credentials credentials = null;
56+
57+
private Map<String, String> maxConnectionsPerHost = Map.of();
58+
59+
private PoolingHttpClientConnectionManager connectionManager;
60+
61+
private HttpClientBuilderCustomizer clientBuilderCustomizer;
62+
63+
private PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer;
64+
65+
/**
66+
* Sets the credentials to be used. If not set, no authentication is done.
67+
*
68+
* @see org.apache.hc.client5.http.auth.UsernamePasswordCredentials
69+
* @see org.apache.hc.client5.http.auth.NTCredentials
70+
*/
71+
public void setCredentials(Credentials credentials) {
72+
this.credentials = credentials;
73+
}
74+
75+
/**
76+
* Sets the authentication scope to be used. Only used when the {@code credentials} property has been set.
77+
* <p>
78+
* By default, the {@link AuthScope#ANY} is used.
79+
*
80+
* @see #setCredentials(Credentials)
81+
*/
82+
public void setAuthScope(AuthScope authScope) {
83+
this.authScope = authScope;
84+
}
85+
86+
/**
87+
* Sets the timeout until a connection is established. A value of 0 means <em>never</em> timeout.
88+
*
89+
* @param timeout the timeout value in milliseconds
90+
*/
91+
public void setConnectionTimeout(int timeout) {
92+
if (timeout < 0) {
93+
throw new IllegalArgumentException("timeout must be a non-negative value");
94+
}
95+
this.connectionTimeout = timeout;
96+
}
97+
98+
/**
99+
* Set the socket read timeout for the underlying HttpClient. A value of 0 means <em>never</em> timeout.
100+
*
101+
* @param timeout the timeout value in milliseconds
102+
*/
103+
public void setReadTimeout(int timeout) {
104+
if (timeout < 0) {
105+
throw new IllegalArgumentException("timeout must be a non-negative value");
106+
}
107+
this.readTimeout = timeout;
108+
}
109+
110+
/**
111+
* Sets the maximum number of connections allowed for the underlying HttpClient.
112+
*
113+
* @param maxTotalConnections the maximum number of connections allowed
114+
* @see PoolingHttpClientConnectionManager...
115+
*/
116+
public void setMaxTotalConnections(int maxTotalConnections) {
117+
if (maxTotalConnections <= 0) {
118+
throw new IllegalArgumentException("maxTotalConnections must be a positive value");
119+
}
120+
this.maxTotalConnections = maxTotalConnections;
121+
}
122+
123+
/**
124+
* Sets the maximum number of connections per host for the underlying HttpClient. The maximum number of connections
125+
* per host can be set in a form accepted by the {@code java.util.Properties} class, like as follows:
126+
*
127+
* <pre>
128+
* https://www.example.com=1
129+
* http://www.example.com:8080=7
130+
* http://www.springframework.org=10
131+
* </pre>
132+
* <p>
133+
* The host can be specified as a URI (with scheme and port).
134+
*
135+
* @param maxConnectionsPerHost a properties object specifying the maximum number of connection
136+
* @see PoolingHttpClientConnectionManager...
137+
*/
138+
public void setMaxConnectionsPerHost(Map<String, String> maxConnectionsPerHost) {
139+
this.maxConnectionsPerHost = maxConnectionsPerHost;
140+
}
141+
142+
void applyMaxConnectionsPerHost(PoolingHttpClientConnectionManager connectionManager) throws URISyntaxException {
143+
144+
for (Map.Entry<String, String> entry : maxConnectionsPerHost.entrySet()) {
145+
URI uri = new URI(entry.getKey());
146+
HttpHost host = new HttpHost(uri.getScheme(), uri.getHost(), getPort(uri));
147+
final HttpRoute route;
148+
149+
if (uri.getScheme().equals("https")) {
150+
route = new HttpRoute(host, null, true);
151+
}
152+
else {
153+
route = new HttpRoute(host);
154+
}
155+
int max = Integer.parseInt(entry.getValue());
156+
connectionManager.setMaxPerRoute(route, max);
157+
}
158+
}
159+
160+
static int getPort(URI uri) {
161+
if (uri.getPort() == -1) {
162+
if ("https".equalsIgnoreCase(uri.getScheme())) {
163+
return 443;
164+
}
165+
if ("http".equalsIgnoreCase(uri.getScheme())) {
166+
return 80;
167+
}
168+
}
169+
return uri.getPort();
170+
}
171+
172+
@Override
173+
public boolean isSingleton() {
174+
return true;
175+
}
176+
177+
@Override
178+
public CloseableHttpClient getObject() throws Exception {
179+
PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder.create();
180+
if (this.maxTotalConnections != -1) {
181+
connectionManagerBuilder.setMaxConnTotal(this.maxTotalConnections);
182+
}
183+
184+
if (null != this.connectionManagerBuilderCustomizer) {
185+
this.connectionManagerBuilderCustomizer.customize(connectionManagerBuilder);
186+
}
187+
188+
this.connectionManager = connectionManagerBuilder.build();
189+
190+
applyMaxConnectionsPerHost(connectionManager);
191+
192+
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom()
193+
.setConnectTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
194+
.setResponseTimeout(readTimeout, TimeUnit.MILLISECONDS);
195+
196+
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
197+
.setDefaultRequestConfig(requestConfigBuilder.build())
198+
.setConnectionManager(connectionManager);
199+
200+
if (null != credentials && null != authScope) {
201+
BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
202+
basicCredentialsProvider.setCredentials(authScope, credentials);
203+
httpClientBuilder.setDefaultCredentialsProvider(basicCredentialsProvider);
204+
}
205+
206+
if (null != this.clientBuilderCustomizer) {
207+
clientBuilderCustomizer.customize(httpClientBuilder);
208+
}
209+
210+
return httpClientBuilder.build();
211+
}
212+
213+
@Override
214+
public Class<?> getObjectType() {
215+
return CloseableHttpClient.class;
216+
}
217+
218+
PoolingHttpClientConnectionManager getConnectionManager() {
219+
return this.connectionManager;
220+
}
221+
222+
public void setClientBuilderCustomizer(HttpClientBuilderCustomizer clientBuilderCustomizer) {
223+
this.clientBuilderCustomizer = clientBuilderCustomizer;
224+
}
225+
226+
public void setConnectionManagerBuilderCustomizer(PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer) {
227+
this.connectionManagerBuilderCustomizer = connectionManagerBuilderCustomizer;
228+
}
229+
230+
@FunctionalInterface
231+
public interface HttpClientBuilderCustomizer {
232+
void customize(HttpClientBuilder httpClientBuilder);
233+
}
234+
235+
@FunctionalInterface
236+
public interface PoolingHttpClientConnectionManagerBuilderCustomizer {
237+
void customize(PoolingHttpClientConnectionManagerBuilder poolingHttpClientConnectionManagerBuilder);
238+
}
239+
}

0 commit comments

Comments
 (0)