diff --git a/CHANGELOG.md b/CHANGELOG.md index acbbccd82..82ca0b436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). # [8.11.0] - 2024-09-24 +- Added custom user agent property setting to `HttpConfig` - Added RCS channel to Messages API - Added `ackInboundMessage` and `revokeOutboundMessage` methods to `MessagesClient` - Fixed `viber_service` deserialization in `com.vonage.client.messages.Channel` diff --git a/src/main/java/com/vonage/client/HttpConfig.java b/src/main/java/com/vonage/client/HttpConfig.java index a500d4d45..df96168a2 100644 --- a/src/main/java/com/vonage/client/HttpConfig.java +++ b/src/main/java/com/vonage/client/HttpConfig.java @@ -27,7 +27,7 @@ public class HttpConfig { DEFAULT_VIDEO_BASE_URI = "https://video.api.vonage.com"; private final int timeoutMillis; - private final String apiBaseUri, restBaseUri, apiEuBaseUri, videoBaseUri; + private final String customUserAgent, apiBaseUri, restBaseUri, apiEuBaseUri, videoBaseUri; private final Function regionalUriGetter; private HttpConfig(Builder builder) { @@ -39,6 +39,7 @@ private HttpConfig(Builder builder) { videoBaseUri = builder.videoBaseUri; apiEuBaseUri = builder.apiEuBaseUri; regionalUriGetter = builder.regionalUriGetter; + customUserAgent = builder.customUserAgent; } /** @@ -78,6 +79,16 @@ public URI getRegionalBaseUri(ApiRegion region) { return URI.create(regionalUriGetter.apply(region)); } + /** + * Returns the custom user agent string that will be appended to the default one, if set. + * + * @return The custom user agent string to append, or {@code null} if not set. + * @since 8.11.0 + */ + public String getCustomUserAgent() { + return customUserAgent; + } + @Deprecated public boolean isDefaultApiBaseUri() { return DEFAULT_API_BASE_URI.equals(apiBaseUri); @@ -131,6 +142,11 @@ public static HttpConfig defaultConfig() { return builder().build(); } + /** + * Entrypoint for creating a custom HttpConfig. + * + * @return A new Builder. + */ public static Builder builder() { return new Builder(); } @@ -141,12 +157,27 @@ public static Builder builder() { public static class Builder { private int timeoutMillis = 60_000; private Function regionalUriGetter = region -> "https://"+region+".vonage.com"; - private String + private String customUserAgent, apiBaseUri = DEFAULT_API_BASE_URI, restBaseUri = DEFAULT_REST_BASE_URI, apiEuBaseUri = DEFAULT_API_EU_BASE_URI, videoBaseUri = DEFAULT_VIDEO_BASE_URI; + /** + * Constructor. + * + * @deprecated Will be made private in the next major version. + */ + @Deprecated + public Builder() {} + + private String sanitizeUri(String uri) { + if (uri != null && uri.endsWith("/")) { + return uri.substring(0, uri.length() - 1); + } + return uri; + } + /** * Sets the socket timeout for requests. By default, this is one minute (60000 ms). *
@@ -249,6 +280,24 @@ public Builder regionalUriGetter(Function uriGetter) { return this; } + /** + * Appends a custom string to the default {@code User-Agent} header. This is mainly used for + * derivatives of the SDK, or to distinguish particular users / use cases. + * + * @param userAgent The user agent string to append to the existing one. Must be less than 128 characters. + * @return This builder. + * @since 8.11.0 + */ + public Builder appendUserAgent(String userAgent) { + if ((this.customUserAgent = Objects.requireNonNull(userAgent).trim()).length() > 127) { + throw new IllegalArgumentException("User agent string must be less than 128 characters in length."); + } + if (customUserAgent.isEmpty()) { + throw new IllegalArgumentException("Custom user agent string cannot be blank."); + } + return this; + } + /** * Builds the HttpConfig. * @@ -257,12 +306,5 @@ public Builder regionalUriGetter(Function uriGetter) { public HttpConfig build() { return new HttpConfig(this); } - - private String sanitizeUri(String uri) { - if (uri != null && uri.endsWith("/")) { - return uri.substring(0, uri.length() - 1); - } - return uri; - } } } diff --git a/src/main/java/com/vonage/client/HttpWrapper.java b/src/main/java/com/vonage/client/HttpWrapper.java index 304c5b05d..cbc700173 100644 --- a/src/main/java/com/vonage/client/HttpWrapper.java +++ b/src/main/java/com/vonage/client/HttpWrapper.java @@ -59,6 +59,11 @@ public HttpWrapper(HttpConfig httpConfig, AuthMethod... authMethods) { this(httpConfig, new AuthCollection(authMethods)); } + /** + * Gets the underlying {@link HttpClient} instance used by the SDK. + * + * @return The Apache HTTP client instance. + */ public HttpClient getHttpClient() { if (httpClient == null) { httpClient = createHttpClient(); @@ -106,6 +111,11 @@ public void setHttpConfig(HttpConfig httpConfig) { this.httpConfig = httpConfig; } + /** + * Gets the authentication settings used by the client. + * + * @return The available authentication methods object. + */ public AuthCollection getAuthCollection() { return authCollection; } @@ -137,18 +147,43 @@ protected HttpClient createHttpClient() { return HttpClientBuilder.create() .setConnectionManager(connectionManager) - .setUserAgent(USER_AGENT) + .setUserAgent(getUserAgent()) .setDefaultRequestConfig(requestConfig) .useSystemProperties() .disableRedirectHandling() .build(); } + /** + * Gets the HTTP configuration settings for the client. + * + * @return The request configuration settings object. + */ public HttpConfig getHttpConfig() { return httpConfig; } + /** + * Gets the {@code User-Agent} header to be used in HTTP requests. + * This includes the Java runtime and SDK version, as well as the custom user agent string, if present. + * + * @return The user agent string. + */ public String getUserAgent() { - return USER_AGENT; + String ua = USER_AGENT, custom = httpConfig.getCustomUserAgent(); + if (custom != null) { + ua += " " + httpConfig.getCustomUserAgent(); + } + return ua; + } + + /** + * Gets the SDK version. + * + * @return The SDK version as a string. + * @since 8.11.0 + */ + public String getClientVersion() { + return CLIENT_VERSION; } } diff --git a/src/test/java/com/vonage/client/HttpConfigTest.java b/src/test/java/com/vonage/client/HttpConfigTest.java index d18b804dd..3d307676d 100644 --- a/src/test/java/com/vonage/client/HttpConfigTest.java +++ b/src/test/java/com/vonage/client/HttpConfigTest.java @@ -110,4 +110,18 @@ public void testApiRegionEnum() { assertEquals(region, ApiRegion.fromString(toString)); } } + + @Test + public void testCustomUserAgentValidation() { + assertEquals("Abc123", HttpConfig.builder().appendUserAgent(" Abc123\t\n").build().getCustomUserAgent()); + assertThrows(NullPointerException.class, () -> + HttpConfig.builder().appendUserAgent(null).build() + ); + assertThrows(IllegalArgumentException.class, () -> + HttpConfig.builder().appendUserAgent(" \t\n").build() + ); + assertThrows(IllegalArgumentException.class, () -> + HttpConfig.builder().appendUserAgent("d".repeat(128)).build() + ); + } } diff --git a/src/test/java/com/vonage/client/HttpWrapperTest.java b/src/test/java/com/vonage/client/HttpWrapperTest.java index ecf5ef6b7..f496eaab6 100644 --- a/src/test/java/com/vonage/client/HttpWrapperTest.java +++ b/src/test/java/com/vonage/client/HttpWrapperTest.java @@ -50,4 +50,21 @@ public void testDefaultConstructorSetsDefaultConfigValues() { assertNotNull(config); HttpConfigTest.assertDefaults(config); } + + @Test + public void testDefaultUserAgent() { + assertNull(wrapper.getHttpConfig().getCustomUserAgent()); + assertEquals( + "vonage-java-sdk/"+wrapper.getClientVersion()+" java/" + System.getProperty("java.version"), + wrapper.getUserAgent() + ); + } + + @Test + public void testValidCustomUserAgent() { + String customUa = "my-custom-agent", defaultUa = wrapper.getUserAgent(); + wrapper = new HttpWrapper(HttpConfig.builder().appendUserAgent(customUa).build()); + assertEquals(customUa, wrapper.getHttpConfig().getCustomUserAgent()); + assertEquals(defaultUa + " " + customUa, wrapper.getUserAgent()); + } }