Skip to content

Commit f250abb

Browse files
committed
Simplify HttpComponents5MessageSender
Previously, HttpComponents5MessageSender can be configured to use both a custom HttpClient or configure one using a number of convenient properties. This setup creates an odd arrangement where calling those convenient methods once an HttpClient is set throws an exception as the implementation supports one or the other. This commit moves the first use case in a simple implementation that only accepts a custom HttpClient or the state of HttpComponents5ClientFactory for convenience and discoverability. Specifying an HttpComponents5MessageSender is deprecated as a result. Closes gh-1519
1 parent cf9295b commit f250abb

File tree

7 files changed

+286
-103
lines changed

7 files changed

+286
-103
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2005-2025 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.ws.transport.http;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
22+
import org.apache.hc.client5.http.classic.HttpClient;
23+
import org.apache.hc.client5.http.classic.methods.HttpPost;
24+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
25+
import org.apache.hc.core5.http.EntityDetails;
26+
import org.apache.hc.core5.http.HttpException;
27+
import org.apache.hc.core5.http.HttpHeaders;
28+
import org.apache.hc.core5.http.HttpHost;
29+
import org.apache.hc.core5.http.HttpRequest;
30+
import org.apache.hc.core5.http.HttpRequestInterceptor;
31+
import org.apache.hc.core5.http.protocol.HttpContext;
32+
33+
import org.springframework.beans.factory.DisposableBean;
34+
import org.springframework.ws.transport.WebServiceConnection;
35+
36+
/**
37+
* Base {@link AbstractHttpWebServiceMessageSender} implementation that uses
38+
* <a href="http://hc.apache.org/httpcomponents-client">Apache HttpClient</a> to execute
39+
* POST requests.
40+
* <p>
41+
* To configure the underlying {@link HttpClient} consider using
42+
* {@link HttpComponents5MessageSender}. To take control on how the {@link HttpClient} is
43+
* built, use {@link SimpleHttpComponents5MessageSender}.
44+
*
45+
* @author Stephane Nicoll
46+
* @since 4.1.0
47+
* @see HttpComponents5MessageSender
48+
* @see SimpleHttpComponents5MessageSender
49+
*/
50+
public abstract class AbstractHttpComponents5MessageSender extends AbstractHttpWebServiceMessageSender
51+
implements DisposableBean {
52+
53+
/**
54+
* Return the {@code HttpClient} used by this message sender.
55+
*/
56+
public abstract HttpClient getHttpClient();
57+
58+
@Override
59+
public WebServiceConnection createConnection(URI uri) throws IOException {
60+
HttpPost httpPost = new HttpPost(uri);
61+
if (isAcceptGzipEncoding()) {
62+
httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
63+
HttpTransportConstants.CONTENT_ENCODING_GZIP);
64+
}
65+
HttpHost httpHost = HttpHost.create(uri);
66+
HttpContext httpContext = createContext(uri);
67+
return new HttpComponents5Connection(getHttpClient(), httpHost, httpPost, httpContext);
68+
}
69+
70+
@Override
71+
public void destroy() throws Exception {
72+
if (getHttpClient() instanceof CloseableHttpClient client) {
73+
client.close();
74+
}
75+
}
76+
77+
/**
78+
* Template method that allows for creation of an {@link HttpContext} for the given
79+
* uri. Default implementation returns {@code null}.
80+
* @param uri the URI to create the context for
81+
* @return the context, or {@code null}
82+
*/
83+
protected HttpContext createContext(URI uri) {
84+
return null;
85+
}
86+
87+
/**
88+
* HttpClient {@link HttpRequestInterceptor} implementation that removes
89+
* {@code Content-Length} and {@code Transfer-Encoding} headers from the request.
90+
* Necessary, because some SAAJ and other SOAP implementations set these headers
91+
* themselves, and HttpClient throws an exception if they have been set.
92+
*/
93+
public static class RemoveSoapHeadersInterceptor implements HttpRequestInterceptor {
94+
95+
@Override
96+
public void process(HttpRequest request, EntityDetails entityDetails, HttpContext httpContext)
97+
throws HttpException, IOException {
98+
99+
if (request.containsHeader(HttpHeaders.TRANSFER_ENCODING)) {
100+
request.removeHeaders(HttpHeaders.TRANSFER_ENCODING);
101+
}
102+
103+
if (request.containsHeader(HttpHeaders.CONTENT_LENGTH)) {
104+
request.removeHeaders(HttpHeaders.CONTENT_LENGTH);
105+
}
106+
}
107+
108+
}
109+
110+
}

Diff for: spring-ws-core/src/main/java/org/springframework/ws/transport/http/HttpComponents5MessageSender.java

+29-96
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,26 @@
1616

1717
package org.springframework.ws.transport.http;
1818

19-
import java.io.IOException;
20-
import java.net.URI;
2119
import java.time.Duration;
2220
import java.util.Map;
2321

2422
import org.apache.hc.client5.http.auth.AuthScope;
2523
import org.apache.hc.client5.http.auth.Credentials;
2624
import org.apache.hc.client5.http.classic.HttpClient;
27-
import org.apache.hc.client5.http.classic.methods.HttpPost;
28-
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
2925
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
3026
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
31-
import org.apache.hc.core5.http.EntityDetails;
32-
import org.apache.hc.core5.http.HttpException;
33-
import org.apache.hc.core5.http.HttpHeaders;
34-
import org.apache.hc.core5.http.HttpHost;
35-
import org.apache.hc.core5.http.HttpRequest;
3627
import org.apache.hc.core5.http.HttpRequestInterceptor;
37-
import org.apache.hc.core5.http.protocol.HttpContext;
3828

39-
import org.springframework.beans.factory.DisposableBean;
4029
import org.springframework.beans.factory.InitializingBean;
4130
import org.springframework.util.Assert;
42-
import org.springframework.ws.transport.WebServiceConnection;
4331

4432
/**
45-
* {@code WebServiceMessageSender} implementation that uses
46-
* <a href="http://hc.apache.org/httpcomponents-client">Apache HttpClient</a> to execute
47-
* POST requests.
33+
* {@code AbstractHttpComponents5MessageSender} implementation that configures the
34+
* underlying <a href="http://hc.apache.org/httpcomponents-client">Apache HttpClient</a>
35+
* that executes POST requests.
4836
* <p>
49-
* Allows to use a pre-configured HttpClient instance, potentially with authentication,
50-
* HTTP connection pooling, etc. Authentication can also be set by injecting a
51-
* {@link Credentials} instance (such as the
52-
* {@link org.apache.hc.client5.http.auth.UsernamePasswordCredentials}).
37+
* To specify the {@link HttpClient}, consider using
38+
* {@link SimpleHttpComponents5MessageSender} instead.
5339
*
5440
* @author Alan Stewart
5541
* @author Barry Pitman
@@ -59,8 +45,7 @@
5945
* @since 4.0.5
6046
* @see HttpClient
6147
*/
62-
public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageSender
63-
implements InitializingBean, DisposableBean {
48+
public class HttpComponents5MessageSender extends AbstractHttpComponents5MessageSender implements InitializingBean {
6449

6550
private static final String HTTP_CLIENT_ALREADY_SET = "httpClient already set";
6651

@@ -87,40 +72,43 @@ public HttpComponents5MessageSender() {
8772
* {@linkplain HttpClientBuilder#addRequestInterceptorFirst(HttpRequestInterceptor)
8873
* add} the {@link RemoveSoapHeadersInterceptor}.
8974
* @param httpClient the HttpClient instance to use for this sender
75+
* @deprecated as of 4.1.0 in favor of {@link SimpleHttpComponents5MessageSender}
9076
*/
77+
@Deprecated(since = "4.1.0", forRemoval = true)
9178
public HttpComponents5MessageSender(HttpClient httpClient) {
9279
this();
9380
Assert.notNull(httpClient, "httpClient must not be null");
9481
this.httpClient = httpClient;
9582
}
9683

97-
/*
98-
* * @see HttpComponents5ClientFactory#setAuthScope(AuthScope)
84+
@Override
85+
public HttpClient getHttpClient() {
86+
return this.httpClient;
87+
}
88+
89+
/**
90+
* Set the authentication scope to be used. Only used when the {@code credentials}
91+
* property has been set.
92+
* @see HttpComponents5ClientFactory#setAuthScope(AuthScope)
9993
*/
10094
public void setAuthScope(AuthScope authScope) {
101-
if (getHttpClient() != null) {
95+
if (this.httpClient != null) {
10296
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
10397
}
10498
this.clientFactory.setAuthScope(authScope);
10599
}
106100

107-
/*
108-
* * @see HttpComponents5ClientFactory#setCredentials(Credentials)
101+
/**
102+
* Set the credentials to be used. If not set, no authentication is done.
103+
* @see HttpComponents5ClientFactory#setCredentials(Credentials)
109104
*/
110105
public void setCredentials(Credentials credentials) {
111-
if (getHttpClient() != null) {
106+
if (this.httpClient != null) {
112107
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
113108
}
114109
this.clientFactory.setCredentials(credentials);
115110
}
116111

117-
/**
118-
* Returns the {@code HttpClient} used by this message sender.
119-
*/
120-
public HttpClient getHttpClient() {
121-
return this.httpClient;
122-
}
123-
124112
/**
125113
* Set the {@code HttpClient} used by this message sender.
126114
* <p>
@@ -129,7 +117,9 @@ public HttpClient getHttpClient() {
129117
* {@linkplain HttpClientBuilder#addRequestInterceptorFirst(HttpRequestInterceptor)
130118
* add} the {@link RemoveSoapHeadersInterceptor}.
131119
* @param httpClient the HttpClient to use
120+
* @deprecated as of 4.1.0 in favor of {@link SimpleHttpComponents5MessageSender}
132121
*/
122+
@Deprecated(since = "4.1.0", forRemoval = true)
133123
public void setHttpClient(HttpClient httpClient) {
134124
this.httpClient = httpClient;
135125
}
@@ -139,7 +129,7 @@ public void setHttpClient(HttpClient httpClient) {
139129
* @see HttpComponents5ClientFactory#setConnectionTimeout(Duration)
140130
*/
141131
public void setConnectionTimeout(Duration timeout) {
142-
if (getHttpClient() != null) {
132+
if (this.httpClient != null) {
143133
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
144134
}
145135
this.clientFactory.setConnectionTimeout(timeout);
@@ -150,7 +140,7 @@ public void setConnectionTimeout(Duration timeout) {
150140
* @see HttpComponents5ClientFactory#setReadTimeout(Duration)
151141
*/
152142
public void setReadTimeout(Duration timeout) {
153-
if (getHttpClient() != null) {
143+
if (this.httpClient != null) {
154144
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
155145
}
156146
this.clientFactory.setReadTimeout(timeout);
@@ -161,7 +151,7 @@ public void setReadTimeout(Duration timeout) {
161151
* @see HttpComponents5ClientFactory#setMaxTotalConnections(int)
162152
*/
163153
public void setMaxTotalConnections(int maxTotalConnections) {
164-
if (getHttpClient() != null) {
154+
if (this.httpClient != null) {
165155
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
166156
}
167157
this.clientFactory.setMaxTotalConnections(maxTotalConnections);
@@ -172,74 +162,17 @@ public void setMaxTotalConnections(int maxTotalConnections) {
172162
* @see HttpComponents5ClientFactory#setMaxConnectionsPerHost(Map)
173163
*/
174164
public void setMaxConnectionsPerHost(Map<String, String> maxConnectionsPerHost) {
175-
if (getHttpClient() != null) {
165+
if (this.httpClient != null) {
176166
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
177167
}
178168
this.clientFactory.setMaxConnectionsPerHost(maxConnectionsPerHost);
179169
}
180170

181171
@Override
182172
public void afterPropertiesSet() throws Exception {
183-
if (getHttpClient() == null) {
173+
if (this.httpClient == null) {
184174
this.httpClient = this.clientFactory.getObject();
185175
}
186176
}
187177

188-
@Override
189-
public WebServiceConnection createConnection(URI uri) throws IOException {
190-
191-
HttpPost httpPost = new HttpPost(uri);
192-
193-
if (isAcceptGzipEncoding()) {
194-
httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
195-
HttpTransportConstants.CONTENT_ENCODING_GZIP);
196-
}
197-
198-
HttpHost httpHost = HttpHost.create(uri);
199-
HttpContext httpContext = createContext(uri);
200-
201-
return new HttpComponents5Connection(getHttpClient(), httpHost, httpPost, httpContext);
202-
}
203-
204-
/**
205-
* Template method that allows for creation of an {@link HttpContext} for the given
206-
* uri. Default implementation returns {@code null}.
207-
* @param uri the URI to create the context for
208-
* @return the context, or {@code null}
209-
*/
210-
protected HttpContext createContext(URI uri) {
211-
return null;
212-
}
213-
214-
@Override
215-
public void destroy() throws Exception {
216-
217-
if (getHttpClient() instanceof CloseableHttpClient client) {
218-
client.close();
219-
}
220-
}
221-
222-
/**
223-
* HttpClient {@link HttpRequestInterceptor} implementation that removes
224-
* {@code Content-Length} and {@code Transfer-Encoding} headers from the request.
225-
* Necessary, because some SAAJ and other SOAP implementations set these headers
226-
* themselves, and HttpClient throws an exception if they have been set.
227-
*/
228-
public static class RemoveSoapHeadersInterceptor implements HttpRequestInterceptor {
229-
230-
@Override
231-
public void process(HttpRequest request, EntityDetails entityDetails, HttpContext httpContext)
232-
throws HttpException, IOException {
233-
234-
if (request.containsHeader(HttpHeaders.TRANSFER_ENCODING)) {
235-
request.removeHeaders(HttpHeaders.TRANSFER_ENCODING);
236-
}
237-
238-
if (request.containsHeader(HttpHeaders.CONTENT_LENGTH)) {
239-
request.removeHeaders(HttpHeaders.CONTENT_LENGTH);
240-
}
241-
}
242-
243-
}
244-
245178
}

0 commit comments

Comments
 (0)