From f250abb0cfa7181c09717a8f0f7f211f46f1cde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 8 Apr 2025 12:59:03 +0200 Subject: [PATCH] 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 --- .../AbstractHttpComponents5MessageSender.java | 110 +++++++++++++++ .../http/HttpComponents5MessageSender.java | 125 ++++-------------- .../SimpleHttpComponents5MessageSender.java | 67 ++++++++++ ...Components5ContentTypeIntegrationTest.java | 9 +- .../HttpComponents5MessageSenderTest.java | 4 + ...impleHttpComponents5MessageSenderTest.java | 72 ++++++++++ spring-ws-docs/src/docs/asciidoc/client.adoc | 2 +- 7 files changed, 286 insertions(+), 103 deletions(-) create mode 100644 spring-ws-core/src/main/java/org/springframework/ws/transport/http/AbstractHttpComponents5MessageSender.java create mode 100644 spring-ws-core/src/main/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSender.java create mode 100644 spring-ws-core/src/test/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSenderTest.java 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 maxConnectionsPerHost) { - if (getHttpClient() != null) { + if (this.httpClient != null) { throw new IllegalStateException(HTTP_CLIENT_ALREADY_SET); } this.clientFactory.setMaxConnectionsPerHost(maxConnectionsPerHost); @@ -180,66 +170,9 @@ public class HttpComponents5MessageSender extends AbstractHttpWebServiceMessageS @Override public void afterPropertiesSet() throws Exception { - if (getHttpClient() == null) { + if (this.httpClient == null) { this.httpClient = this.clientFactory.getObject(); } } - @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); - } - - /** - * 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; - } - - @Override - public void destroy() throws Exception { - - if (getHttpClient() instanceof CloseableHttpClient client) { - client.close(); - } - } - - /** - * 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/SimpleHttpComponents5MessageSender.java b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSender.java new file mode 100644 index 00000000..c1fa0225 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSender.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.hc.client5.http.classic.HttpClient; + +import org.springframework.util.Assert; + +/** + * {@code AbstractHttpComponents5MessageSender} implementation that defines the underlying + * Apache HttpClient that + * executes POST requests. + *

+ * 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 { + extends AbstractHttpWebServiceMessageSenderIntegrationTest { @Override - protected HttpComponents5MessageSender createMessageSender() { - + protected AbstractHttpComponents5MessageSender createMessageSender() { ExecChainHandler testHandler = (request, scope, chain) -> { assertThat(request.getEntity().getContentType()).isNotBlank(); return chain.proceed(request, scope); }; - HttpClient client = HttpClientBuilder.create() .addRequestInterceptorFirst(new HttpComponents5MessageSender.RemoveSoapHeadersInterceptor()) .addExecInterceptorFirst("handler with assertion", testHandler) .build(); - - return new HttpComponents5MessageSender(client); + return new SimpleHttpComponents5MessageSender(client); } } diff --git a/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5MessageSenderTest.java b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5MessageSenderTest.java index 97d7069f..5355c82e 100644 --- a/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5MessageSenderTest.java +++ b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/HttpComponents5MessageSenderTest.java @@ -44,6 +44,8 @@ class HttpComponents5MessageSenderTest { } @Test + @Deprecated + @SuppressWarnings("removal") void afterPropertiesSetShouldUseAlreadyProvidedHttpClientIfAvailableWithConstructor() throws Exception { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpComponents5MessageSender messageSender = new HttpComponents5MessageSender(httpClient); @@ -54,6 +56,8 @@ class HttpComponents5MessageSenderTest { } @Test + @Deprecated + @SuppressWarnings("removal") void afterPropertiesSetShouldUseAlreadyProvidedHttpClientIfAvailableWithProperty() throws Exception { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpComponents5MessageSender messageSender = new HttpComponents5MessageSender(); diff --git a/spring-ws-core/src/test/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSenderTest.java b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSenderTest.java new file mode 100644 index 00000000..8149fec2 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/transport/http/SimpleHttpComponents5MessageSenderTest.java @@ -0,0 +1,72 @@ +/* + * 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.impl.classic.HttpClientBuilder; +import org.junit.jupiter.api.Test; + +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.http.HttpComponents5ClientFactory.HttpClientBuilderCustomizer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link SimpleHttpComponents5MessageSender}. + */ +class SimpleHttpComponents5MessageSenderTest { + + @Test + void createExposesHttpClient() { + HttpClient httpClient = HttpClientBuilder.create().build(); + SimpleHttpComponents5MessageSender messageSender = new SimpleHttpComponents5MessageSender(httpClient); + assertThat(messageSender.getHttpClient()).isSameAs(httpClient); + } + + @Test + void createWithNullFails() { + HttpClient httpClient = null; + assertThatIllegalArgumentException().isThrownBy(() -> new SimpleHttpComponents5MessageSender(httpClient)); + } + + @Test + void createWithFactory() throws Exception { + HttpComponents5ClientFactory factory = new HttpComponents5ClientFactory(); + HttpClientBuilderCustomizer builderCustomizer = mock(HttpClientBuilderCustomizer.class); + factory.setClientBuilderCustomizer(builderCustomizer); + new SimpleHttpComponents5MessageSender(factory); + verify(builderCustomizer).customize(any()); + } + + @Test + void createConnectionUsesHttpClient() throws IOException { + HttpClient httpClient = HttpClientBuilder.create().build(); + SimpleHttpComponents5MessageSender messageSender = new SimpleHttpComponents5MessageSender(httpClient); + WebServiceConnection webServiceConnection = messageSender + .createConnection(URI.create("https://ws.example.com")); + assertThat(webServiceConnection).isInstanceOfSatisfying(HttpComponents5Connection.class, + (connection) -> assertThat(connection).hasFieldOrPropertyWithValue("httpClient", httpClient)); + } + +} \ No newline at end of file diff --git a/spring-ws-docs/src/docs/asciidoc/client.adoc b/spring-ws-docs/src/docs/asciidoc/client.adoc index 24f981a6..4569c911 100644 --- a/spring-ws-docs/src/docs/asciidoc/client.adoc +++ b/spring-ws-docs/src/docs/asciidoc/client.adoc @@ -38,7 +38,7 @@ You can set one or more message senders by using the `messageSender` or `message There are three implementations of the `WebServiceMessageSender` interface for sending messages over HTTP. The default implementation is the `HttpUrlConnectionMessageSender`, which uses the facilities provided by Java itself. -The alternatives are either `JdkHttpClientMessageSender` that uses the JDK's `HttpClient`, or `HttpComponents5MessageSender`, which uses the https://hc.apache.org/httpcomponents-client-ga[Apache HttpClient]. +The alternatives are either `JdkHttpClientMessageSender` that uses the JDK's `HttpClient`, or `HttpComponents5MessageSender`/`SimpleHttpComponents5MessageSender`, which use the https://hc.apache.org/httpcomponents-client-ga[Apache HttpClient]. Use the latter if you need more advanced and easy-to-use functionality (such as authentication, HTTP connection pooling, and so forth). To use the HTTP transport, either set the `defaultUri` to something like `http://example.com/services` or supply the `uri` parameter for one of the methods.