1616
1717package org .springframework .ws .transport .http ;
1818
19+ import java .io .IOException ;
1920import java .net .URI ;
20- import java .net .URISyntaxException ;
2121import java .time .Duration ;
22+ import java .util .ArrayList ;
23+ import java .util .List ;
2224import java .util .Map ;
2325
2426import org .apache .hc .client5 .http .HttpRoute ;
3032import org .apache .hc .client5 .http .impl .classic .HttpClientBuilder ;
3133import org .apache .hc .client5 .http .impl .io .PoolingHttpClientConnectionManager ;
3234import org .apache .hc .client5 .http .impl .io .PoolingHttpClientConnectionManagerBuilder ;
35+ import org .apache .hc .core5 .http .EntityDetails ;
36+ import org .apache .hc .core5 .http .HttpException ;
37+ import org .apache .hc .core5 .http .HttpHeaders ;
3338import org .apache .hc .core5 .http .HttpHost ;
39+ import org .apache .hc .core5 .http .HttpRequest ;
40+ import org .apache .hc .core5 .http .HttpRequestInterceptor ;
41+ import org .apache .hc .core5 .http .protocol .HttpContext ;
3442import org .apache .hc .core5 .util .Timeout ;
3543
3644import org .springframework .beans .factory .FactoryBean ;
4149 * HttpClient 5.
4250 *
4351 * @author Lars Uffmann
52+ * @author Stephane Nicoll
4453 * @since 4.0.5
4554 * @see <a href=https://hc.apache.org/httpcomponents-client>HttpComponents</a>
4655 */
@@ -60,6 +69,10 @@ public class HttpComponents5ClientFactory implements FactoryBean<CloseableHttpCl
6069
6170 private static final Duration DEFAULT_READ_TIMEOUT = Duration .ofSeconds (60 );
6271
72+ private final List <HttpClientBuilderCustomizer > clientBuilderCustomizers = new ArrayList <>();
73+
74+ private final List <PoolingHttpClientConnectionManagerBuilderCustomizer > connectionManagerBuilderCustomizers = new ArrayList <>();
75+
6376 private Duration connectionTimeout = DEFAULT_CONNECTION_TIMEOUT ;
6477
6578 private Duration readTimeout = DEFAULT_READ_TIMEOUT ;
@@ -74,9 +87,62 @@ public class HttpComponents5ClientFactory implements FactoryBean<CloseableHttpCl
7487
7588 private PoolingHttpClientConnectionManager connectionManager ;
7689
77- private HttpClientBuilderCustomizer clientBuilderCustomizer ;
90+ /**
91+ * Create a new instance with default settings. This configures
92+ * {@link RemoveSoapHeadersInterceptor} as the first interceptor
93+ * @return a factory with default settings
94+ */
95+ public static HttpComponents5ClientFactory withDefaults () {
96+ HttpComponents5ClientFactory factory = new HttpComponents5ClientFactory ();
97+ factory .addClientBuilderCustomizer ((httpClientBuilder ) -> httpClientBuilder
98+ .addRequestInterceptorFirst (new RemoveSoapHeadersInterceptor ()));
99+ return factory ;
100+ }
101+
102+ /**
103+ * Add a {@link HttpClientBuilderCustomizer} to invoke when creating an
104+ * {@link CloseableHttpClient} managed by this factory.
105+ * @param clientBuilderCustomizer the customizer to invoke
106+ * @since 4.1.0
107+ */
108+ public void addClientBuilderCustomizer (HttpClientBuilderCustomizer clientBuilderCustomizer ) {
109+ this .clientBuilderCustomizers .add (clientBuilderCustomizer );
110+ }
111+
112+ /**
113+ * Add a {@link HttpClientBuilderCustomizer} to invoke when creating an
114+ * {@link CloseableHttpClient} managed by this factory.
115+ * @param clientBuilderCustomizer the customizer to invoke
116+ * @deprecated as of 4.1.0 in favor of
117+ * {@link #addClientBuilderCustomizer(HttpClientBuilderCustomizer)}l
118+ */
119+ @ Deprecated (since = "4.1.0" , forRemoval = true )
120+ public void setClientBuilderCustomizer (HttpClientBuilderCustomizer clientBuilderCustomizer ) {
121+ addClientBuilderCustomizer (clientBuilderCustomizer );
122+ }
123+
124+ /**
125+ * Add a {@link PoolingHttpClientConnectionManagerBuilderCustomizer} to invoke when
126+ * creating an {@link CloseableHttpClient} managed by this factory.
127+ * @param connectionManagerBuilderCustomizer the customizer to invoke
128+ */
129+ public void addConnectionManagerBuilderCustomizer (
130+ PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer ) {
131+ this .connectionManagerBuilderCustomizers .add (connectionManagerBuilderCustomizer );
132+ }
78133
79- private PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer ;
134+ /**
135+ * Add a {@link PoolingHttpClientConnectionManagerBuilderCustomizer} to invoke when
136+ * creating an {@link CloseableHttpClient} managed by this factory.
137+ * @param connectionManagerBuilderCustomizer the customizer to invoke
138+ * @deprecated as of 4.1.0 in favor of
139+ * {@link #addConnectionManagerBuilderCustomizer(PoolingHttpClientConnectionManagerBuilderCustomizer)}
140+ */
141+ @ Deprecated (since = "4.1.0" , forRemoval = true )
142+ public void setConnectionManagerBuilderCustomizer (
143+ PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer ) {
144+ addConnectionManagerBuilderCustomizer (connectionManagerBuilderCustomizer );
145+ }
80146
81147 /**
82148 * Sets the credentials to be used. If not set, no authentication is done.
@@ -104,11 +170,9 @@ public void setAuthScope(AuthScope authScope) {
104170 * @param timeout the timeout value
105171 */
106172 public void setConnectionTimeout (Duration timeout ) {
107-
108173 if (timeout .isNegative ()) {
109174 throw new IllegalArgumentException ("timeout must be a non-negative value" );
110175 }
111-
112176 this .connectionTimeout = timeout ;
113177 }
114178
@@ -118,11 +182,9 @@ public void setConnectionTimeout(Duration timeout) {
118182 * @param timeout the timeout value
119183 */
120184 public void setReadTimeout (Duration timeout ) {
121-
122185 if (timeout .isNegative ()) {
123186 throw new IllegalArgumentException ("timeout must be a non-negative value" );
124187 }
125-
126188 this .readTimeout = timeout ;
127189 }
128190
@@ -132,11 +194,9 @@ public void setReadTimeout(Duration timeout) {
132194 * @see PoolingHttpClientConnectionManager
133195 */
134196 public void setMaxTotalConnections (int maxTotalConnections ) {
135-
136197 if (maxTotalConnections <= 0 ) {
137198 throw new IllegalArgumentException ("maxTotalConnections must be a positive value" );
138199 }
139-
140200 this .maxTotalConnections = maxTotalConnections ;
141201 }
142202
@@ -160,11 +220,44 @@ public void setMaxConnectionsPerHost(Map<String, String> maxConnectionsPerHost)
160220 this .maxConnectionsPerHost = maxConnectionsPerHost ;
161221 }
162222
163- void applyMaxConnectionsPerHost (PoolingHttpClientConnectionManager connectionManager ) throws URISyntaxException {
223+ PoolingHttpClientConnectionManager getConnectionManager () {
224+ return this .connectionManager ;
225+ }
164226
165- for (Map .Entry <String , String > entry : this .maxConnectionsPerHost .entrySet ()) {
227+ @ SuppressWarnings ("deprecation" )
228+ public CloseableHttpClient build () {
229+ PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder
230+ .create ();
231+ if (this .maxTotalConnections != -1 ) {
232+ connectionManagerBuilder .setMaxConnTotal (this .maxTotalConnections );
233+ }
234+ this .connectionManagerBuilderCustomizers .forEach (customizer -> customizer .customize (connectionManagerBuilder ));
235+ this .connectionManager = connectionManagerBuilder .build ();
236+
237+ applyMaxConnectionsPerHost (this .connectionManager );
238+
239+ RequestConfig .Builder requestConfigBuilder = RequestConfig .custom ()
240+ .setConnectTimeout (Timeout .of (this .connectionTimeout ))
241+ .setResponseTimeout (Timeout .of (this .readTimeout ));
242+
243+ HttpClientBuilder httpClientBuilder = HttpClientBuilder .create ()
244+ .setDefaultRequestConfig (requestConfigBuilder .build ())
245+ .setConnectionManager (this .connectionManager );
246+
247+ if (this .credentials != null && this .authScope != null ) {
248+ BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider ();
249+ basicCredentialsProvider .setCredentials (this .authScope , this .credentials );
250+ httpClientBuilder .setDefaultCredentialsProvider (basicCredentialsProvider );
251+ }
252+
253+ this .clientBuilderCustomizers .forEach (customizer -> customizer .customize (httpClientBuilder ));
166254
167- URI uri = new URI (entry .getKey ());
255+ return httpClientBuilder .build ();
256+ }
257+
258+ void applyMaxConnectionsPerHost (PoolingHttpClientConnectionManager connectionManager ) {
259+ for (Map .Entry <String , String > entry : this .maxConnectionsPerHost .entrySet ()) {
260+ URI uri = URI .create (entry .getKey ());
168261 HttpHost host = new HttpHost (uri .getScheme (), uri .getHost (), getPort (uri ));
169262 final HttpRoute route ;
170263
@@ -180,17 +273,14 @@ void applyMaxConnectionsPerHost(PoolingHttpClientConnectionManager connectionMan
180273 }
181274
182275 static int getPort (URI uri ) {
183-
184276 if (uri .getPort () == -1 ) {
185-
186277 if ("https" .equalsIgnoreCase (uri .getScheme ())) {
187278 return 443 ;
188279 }
189280 if ("http" .equalsIgnoreCase (uri .getScheme ())) {
190281 return 80 ;
191282 }
192283 }
193-
194284 return uri .getPort ();
195285 }
196286
@@ -200,62 +290,36 @@ public boolean isSingleton() {
200290 }
201291
202292 @ Override
203- @ SuppressWarnings ("deprecation" )
204293 public CloseableHttpClient getObject () throws Exception {
205-
206- PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder
207- .create ();
208-
209- if (this .maxTotalConnections != -1 ) {
210- connectionManagerBuilder .setMaxConnTotal (this .maxTotalConnections );
211- }
212-
213- if (this .connectionManagerBuilderCustomizer != null ) {
214- this .connectionManagerBuilderCustomizer .customize (connectionManagerBuilder );
215- }
216-
217- this .connectionManager = connectionManagerBuilder .build ();
218-
219- applyMaxConnectionsPerHost (this .connectionManager );
220-
221- RequestConfig .Builder requestConfigBuilder = RequestConfig .custom ()
222- .setConnectTimeout (Timeout .of (this .connectionTimeout ))
223- .setResponseTimeout (Timeout .of (this .readTimeout ));
224-
225- HttpClientBuilder httpClientBuilder = HttpClientBuilder .create ()
226- .setDefaultRequestConfig (requestConfigBuilder .build ())
227- .setConnectionManager (this .connectionManager );
228-
229- if (this .credentials != null && this .authScope != null ) {
230-
231- BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider ();
232- basicCredentialsProvider .setCredentials (this .authScope , this .credentials );
233- httpClientBuilder .setDefaultCredentialsProvider (basicCredentialsProvider );
234- }
235-
236- if (this .clientBuilderCustomizer != null ) {
237- this .clientBuilderCustomizer .customize (httpClientBuilder );
238- }
239-
240- return httpClientBuilder .build ();
294+ return build ();
241295 }
242296
243297 @ Override
244298 public Class <?> getObjectType () {
245299 return CloseableHttpClient .class ;
246300 }
247301
248- PoolingHttpClientConnectionManager getConnectionManager () {
249- return this .connectionManager ;
250- }
302+ /**
303+ * HttpClient {@link HttpRequestInterceptor} implementation that removes
304+ * {@code Content-Length} and {@code Transfer-Encoding} headers from the request.
305+ * Necessary, because some SAAJ and other SOAP implementations set these headers
306+ * themselves, and HttpClient throws an exception if they have been set.
307+ */
308+ public static class RemoveSoapHeadersInterceptor implements HttpRequestInterceptor {
251309
252- public void setClientBuilderCustomizer (HttpClientBuilderCustomizer clientBuilderCustomizer ) {
253- this .clientBuilderCustomizer = clientBuilderCustomizer ;
254- }
310+ @ Override
311+ public void process (HttpRequest request , EntityDetails entityDetails , HttpContext httpContext )
312+ throws HttpException , IOException {
313+
314+ if (request .containsHeader (HttpHeaders .TRANSFER_ENCODING )) {
315+ request .removeHeaders (HttpHeaders .TRANSFER_ENCODING );
316+ }
317+
318+ if (request .containsHeader (HttpHeaders .CONTENT_LENGTH )) {
319+ request .removeHeaders (HttpHeaders .CONTENT_LENGTH );
320+ }
321+ }
255322
256- public void setConnectionManagerBuilderCustomizer (
257- PoolingHttpClientConnectionManagerBuilderCustomizer connectionManagerBuilderCustomizer ) {
258- this .connectionManagerBuilderCustomizer = connectionManagerBuilderCustomizer ;
259323 }
260324
261325 @ FunctionalInterface
0 commit comments