diff --git a/spring-ws-core/src/main/java/org/springframework/ws/transport/http/AbstractHttpComponents5MessageSender.java b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/AbstractHttpComponents5MessageSender.java new file mode 100644 index 00000000..1256a5bc --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/AbstractHttpComponents5MessageSender.java @@ -0,0 +1,110 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http; + +import java.io.IOException; +import java.net.URI; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.protocol.HttpContext; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.ws.transport.WebServiceConnection; + +/** + * Base {@link AbstractHttpWebServiceMessageSender} implementation that uses + * Apache HttpClient to execute + * POST requests. + *
+ * To configure the underlying {@link HttpClient} consider using + * {@link HttpComponents5MessageSender}. To take control on how the {@link HttpClient} is + * built, use {@link SimpleHttpComponents5MessageSender}. + * + * @author Stephane Nicoll + * @since 4.1.0 + * @see HttpComponents5MessageSender + * @see SimpleHttpComponents5MessageSender + */ +public abstract class AbstractHttpComponents5MessageSender extends AbstractHttpWebServiceMessageSender + implements DisposableBean { + + /** + * Return the {@code HttpClient} used by this message sender. + */ + public abstract HttpClient getHttpClient(); + + @Override + public WebServiceConnection createConnection(URI uri) throws IOException { + HttpPost httpPost = new HttpPost(uri); + if (isAcceptGzipEncoding()) { + httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING, + HttpTransportConstants.CONTENT_ENCODING_GZIP); + } + HttpHost httpHost = HttpHost.create(uri); + HttpContext httpContext = createContext(uri); + return new HttpComponents5Connection(getHttpClient(), httpHost, httpPost, httpContext); + } + + @Override + public void destroy() throws Exception { + if (getHttpClient() instanceof CloseableHttpClient client) { + client.close(); + } + } + + /** + * Template method that allows for creation of an {@link HttpContext} for the given + * uri. Default implementation returns {@code null}. + * @param uri the URI to create the context for + * @return the context, or {@code null} + */ + protected HttpContext createContext(URI uri) { + return null; + } + + /** + * HttpClient {@link HttpRequestInterceptor} implementation that removes + * {@code Content-Length} and {@code Transfer-Encoding} headers from the request. + * Necessary, because some SAAJ and other SOAP implementations set these headers + * themselves, and HttpClient throws an exception if they have been set. + */ + public static class RemoveSoapHeadersInterceptor implements HttpRequestInterceptor { + + @Override + public void process(HttpRequest request, EntityDetails entityDetails, HttpContext httpContext) + throws HttpException, IOException { + + if (request.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { + request.removeHeaders(HttpHeaders.TRANSFER_ENCODING); + } + + if (request.containsHeader(HttpHeaders.CONTENT_LENGTH)) { + request.removeHeaders(HttpHeaders.CONTENT_LENGTH); + } + } + + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/transport/http/HttpComponents5MessageSender.java b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/HttpComponents5MessageSender.java index 2cdb52a4..ed1c86b9 100644 --- a/spring-ws-core/src/main/java/org/springframework/ws/transport/http/HttpComponents5MessageSender.java +++ b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/HttpComponents5MessageSender.java @@ -16,40 +16,26 @@ package org.springframework.ws.transport.http; -import java.io.IOException; -import java.net.URI; import java.time.Duration; import java.util.Map; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.classic.HttpClient; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.core5.http.EntityDetails; -import org.apache.hc.core5.http.HttpException; -import org.apache.hc.core5.http.HttpHeaders; -import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; -import org.apache.hc.core5.http.protocol.HttpContext; -import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.ws.transport.WebServiceConnection; /** - * {@code WebServiceMessageSender} implementation that uses - * Apache HttpClient to execute - * POST requests. + * {@code AbstractHttpComponents5MessageSender} implementation that configures the + * underlying Apache HttpClient + * that executes POST requests. *
- * Allows to use a pre-configured HttpClient instance, potentially with authentication, - * HTTP connection pooling, etc. Authentication can also be set by injecting a - * {@link Credentials} instance (such as the - * {@link org.apache.hc.client5.http.auth.UsernamePasswordCredentials}). + * To specify the {@link HttpClient}, consider using + * {@link SimpleHttpComponents5MessageSender} instead. * * @author Alan Stewart * @author Barry Pitman @@ -59,8 +45,7 @@ import org.springframework.ws.transport.WebServiceConnection; * @since 4.0.5 * @see HttpClient */ -public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageSender - implements InitializingBean, DisposableBean { +public class HttpComponents5MessageSender extends AbstractHttpComponents5MessageSender implements InitializingBean { private static final String HTTP_CLIENT_ALREADY_SET = "httpClient already set"; @@ -87,40 +72,43 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS * {@linkplain HttpClientBuilder#addRequestInterceptorFirst(HttpRequestInterceptor) * add} the {@link RemoveSoapHeadersInterceptor}. * @param httpClient the HttpClient instance to use for this sender + * @deprecated as of 4.1.0 in favor of {@link SimpleHttpComponents5MessageSender} */ + @Deprecated(since = "4.1.0", forRemoval = true) public HttpComponents5MessageSender(HttpClient httpClient) { this(); Assert.notNull(httpClient, "httpClient must not be null"); this.httpClient = httpClient; } - /* - * * @see HttpComponents5ClientFactory#setAuthScope(AuthScope) + @Override + public HttpClient getHttpClient() { + return this.httpClient; + } + + /** + * Set the authentication scope to be used. Only used when the {@code credentials} + * property has been set. + * @see HttpComponents5ClientFactory#setAuthScope(AuthScope) */ public void setAuthScope(AuthScope authScope) { - if (getHttpClient() != null) { + if (this.httpClient != null) { throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET); } this.clientFactory.setAuthScope(authScope); } - /* - * * @see HttpComponents5ClientFactory#setCredentials(Credentials) + /** + * Set the credentials to be used. If not set, no authentication is done. + * @see HttpComponents5ClientFactory#setCredentials(Credentials) */ public void setCredentials(Credentials credentials) { - if (getHttpClient() != null) { + if (this.httpClient != null) { throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET); } this.clientFactory.setCredentials(credentials); } - /** - * Returns the {@code HttpClient} used by this message sender. - */ - public HttpClient getHttpClient() { - return this.httpClient; - } - /** * Set the {@code HttpClient} used by this message sender. *
@@ -129,7 +117,9 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS
* {@linkplain HttpClientBuilder#addRequestInterceptorFirst(HttpRequestInterceptor)
* add} the {@link RemoveSoapHeadersInterceptor}.
* @param httpClient the HttpClient to use
+ * @deprecated as of 4.1.0 in favor of {@link SimpleHttpComponents5MessageSender}
*/
+ @Deprecated(since = "4.1.0", forRemoval = true)
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
@@ -139,7 +129,7 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS
* @see HttpComponents5ClientFactory#setConnectionTimeout(Duration)
*/
public void setConnectionTimeout(Duration timeout) {
- if (getHttpClient() != null) {
+ if (this.httpClient != null) {
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
}
this.clientFactory.setConnectionTimeout(timeout);
@@ -150,7 +140,7 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS
* @see HttpComponents5ClientFactory#setReadTimeout(Duration)
*/
public void setReadTimeout(Duration timeout) {
- if (getHttpClient() != null) {
+ if (this.httpClient != null) {
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
}
this.clientFactory.setReadTimeout(timeout);
@@ -161,7 +151,7 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS
* @see HttpComponents5ClientFactory#setMaxTotalConnections(int)
*/
public void setMaxTotalConnections(int maxTotalConnections) {
- if (getHttpClient() != null) {
+ if (this.httpClient != null) {
throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET);
}
this.clientFactory.setMaxTotalConnections(maxTotalConnections);
@@ -172,7 +162,7 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS
* @see HttpComponents5ClientFactory#setMaxConnectionsPerHost(Map)
*/
public void setMaxConnectionsPerHost(Map
+ * The {@link HttpClient} can be provided as-is or configured via the convenient
+ * {@link HttpComponents5ClientFactory}
+ *
+ * For convenience method use to customize the underlying {@link HttpClient}, consider
+ * using {@link HttpComponents5MessageSender} instead.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ * @see HttpClient
+ * @see HttpComponents5ClientFactory
+ */
+public class SimpleHttpComponents5MessageSender extends AbstractHttpComponents5MessageSender {
+
+ private final HttpClient httpClient;
+
+ /**
+ * Creates an instance with the given {@link HttpClient}.
+ * @param httpClient the http client to use
+ */
+ public SimpleHttpComponents5MessageSender(HttpClient httpClient) {
+ Assert.notNull(httpClient, "httpClient must not be null");
+ this.httpClient = httpClient;
+ }
+
+ /**
+ * Create a new instance with the state of the given
+ * {@link HttpComponents5ClientFactory}.
+ * @param factory the factory to use
+ * @throws Exception if the client fails to build
+ */
+ public SimpleHttpComponents5MessageSender(HttpComponents5ClientFactory factory) throws Exception {
+ this(factory.getObject());
+ }
+
+ @Override
+ public HttpClient getHttpClient() {
+ return this.httpClient;
+ }
+
+}
diff --git a/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5ContentTypeIntegrationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5ContentTypeIntegrationTest.java
index 3e8d82e9..930c4952 100644
--- a/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5ContentTypeIntegrationTest.java
+++ b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5ContentTypeIntegrationTest.java
@@ -23,22 +23,19 @@ import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
class HttpComponents5ContentTypeIntegrationTest
- extends AbstractHttpWebServiceMessageSenderIntegrationTest