Skip to content

Commit df57122

Browse files
authored
feat: Add support for custom HTTP request headers (#585)
* fix: Make HttpWrapper#setHttpClient public * feat: Support custom request headers * refactor: Immutable custom headers map
1 parent 8bc8204 commit df57122

File tree

9 files changed

+181
-17
lines changed

9 files changed

+181
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
# [9.2.0] - 2025-04-30
6+
- Added support for setting additional request headers in `HttpConfig`
7+
- Allow setting `HttpClient` and `HttpConfig` on `HttpWrapper`
8+
- Bumped Jackson version to 2.19.0
9+
510
# [9.1.0] - 2025-04-22
611
- Added custom HTTP requests support via `CustomClient` (see [README](README.md#custom-requests) for details)
712

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Add the following to your `build.gradle` or `build.gradle.kts` file:
7474

7575
```groovy
7676
dependencies {
77-
implementation("com.vonage:server-sdk:9.1.0")
77+
implementation("com.vonage:server-sdk:9.2.0")
7878
}
7979
```
8080

@@ -85,7 +85,7 @@ Add the following to the `<dependencies>` section of your `pom.xml` file:
8585
<dependency>
8686
<groupId>com.vonage</groupId>
8787
<artifactId>server-sdk</artifactId>
88-
<version>9.1.0</version>
88+
<version>9.2.0</version>
8989
</dependency>
9090
```
9191

@@ -248,6 +248,35 @@ VonageClient client = VonageClient.builder()
248248
.build();
249249
```
250250

251+
### Proxy
252+
253+
You can set a proxy server for requests using the `proxy` method on `HttpConfig.Builder`.
254+
255+
```java
256+
VonageClient client = VonageClient.builder()
257+
.applicationId(APPLICATION_ID)
258+
.privateKeyPath(PRIVATE_KEY_PATH)
259+
.httpConfig(HttpConfig.builder().proxy("https://myserver.example.com").build())
260+
.build();
261+
```
262+
263+
264+
### Request Headers
265+
266+
With the `HttpConfig` class, you can also set custom request headers for all requests made by the SDK.
267+
268+
```java
269+
VonageClient client = VonageClient.builder()
270+
.applicationId(APPLICATION_ID)
271+
.privateKeyPath(PRIVATE_KEY_PATH)
272+
.httpConfig(HttpConfig.builder()
273+
.addRequestHeader("X-My-Header", "MyValue")
274+
.addRequestHeader("Correlation-Id", "123-456-789")
275+
.build()
276+
)
277+
.build();
278+
```
279+
251280
### Logging
252281

253282
The SDK uses [Java's built-in logging library (`java.util.logging`)](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) to log requests and responses.

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.vonage</groupId>
77
<artifactId>server-sdk</artifactId>
8-
<version>9.1.0</version>
8+
<version>9.2.0</version>
99

1010
<name>Vonage Java Server SDK</name>
1111
<description>Java client for Vonage APIs</description>
@@ -45,7 +45,7 @@
4545
<java.testVersion>21</java.testVersion>
4646
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4747
<nexusUrl>https://oss.sonatype.org</nexusUrl>
48-
<jackson.version>2.18.3</jackson.version>
48+
<jackson.version>2.19.0</jackson.version>
4949
<mockito.version>5.17.0</mockito.version>
5050
<jjwt.version>0.12.6</jjwt.version>
5151
</properties>

src/main/java/com/vonage/client/AbstractMethod.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* Abstract class to assist in implementing a call against a REST endpoint.
3535
* <p>
3636
* Concrete implementations must implement {@link #makeRequest(Object)} to construct a {@link RequestBuilder} from the
37-
* provided parameterized request object, and {@link #parseResponse(HttpResponse)} to construct the parameterized
37+
* provided parameterised request object, and {@link #parseResponse(HttpResponse)} to construct the parameterized
3838
* {@link HttpResponse} object.
3939
* <p>
4040
* The REST call is executed by calling {@link #execute(Object)}.
@@ -86,9 +86,10 @@ protected RES postProcessParsedResponse(RES response) {
8686
return response;
8787
}
8888

89-
private HttpUriRequest createFullHttpRequest(REQ request) throws VonageClientException {
90-
return applyAuth(makeRequest(request))
91-
.setHeader(HttpHeaders.USER_AGENT, httpWrapper.getUserAgent())
89+
HttpUriRequest createFullHttpRequest(REQ request) throws VonageClientException {
90+
RequestBuilder rqb = applyAuth(makeRequest(request));
91+
httpWrapper.getHttpConfig().getCustomHeaders().forEach(rqb::setHeader);
92+
return rqb.setHeader(HttpHeaders.USER_AGENT, httpWrapper.getUserAgent())
9293
.setCharset(StandardCharsets.UTF_8).build();
9394
}
9495

src/main/java/com/vonage/client/HttpConfig.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package com.vonage.client;
1717

1818
import java.net.URI;
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
1921
import java.util.Objects;
2022
import java.util.function.Function;
2123

@@ -30,6 +32,7 @@ public class HttpConfig {
3032
private final String customUserAgent, apiBaseUri, restBaseUri, apiEuBaseUri, videoBaseUri;
3133
private final Function<ApiRegion, String> regionalUriGetter;
3234
private final URI proxy;
35+
private final Map<String, String> customHeaders;
3336

3437
private HttpConfig(Builder builder) {
3538
if ((timeoutMillis = builder.timeoutMillis) < 10) {
@@ -42,6 +45,7 @@ private HttpConfig(Builder builder) {
4245
apiEuBaseUri = builder.apiEuBaseUri;
4346
regionalUriGetter = builder.regionalUriGetter;
4447
customUserAgent = builder.customUserAgent;
48+
customHeaders = builder.customHeaders;
4549
}
4650

4751
/**
@@ -54,18 +58,39 @@ public int getTimeoutMillis() {
5458
return timeoutMillis;
5559
}
5660

61+
/**
62+
* Returns the base URI for the "api" endpoints.
63+
*
64+
* @return The base URI for the "api" endpoints.
65+
*/
5766
public String getApiBaseUri() {
5867
return apiBaseUri;
5968
}
6069

70+
/**
71+
* Returns the base URI for the "rest" endpoints.
72+
*
73+
* @return The base URI for the "rest" endpoints.
74+
*/
6175
public String getRestBaseUri() {
6276
return restBaseUri;
6377
}
6478

79+
/**
80+
* Returns the base URI for the "video" endpoints.
81+
*
82+
* @return The base URI for the "video" endpoints.
83+
* @since 8.0.0
84+
*/
6585
public String getVideoBaseUri() {
6686
return videoBaseUri;
6787
}
6888

89+
/**
90+
* Returns the base URI for the "api-eu" endpoints.
91+
*
92+
* @return The base URI for the "api-eu" endpoints.
93+
*/
6994
public String getApiEuBaseUri() {
7095
return apiEuBaseUri;
7196
}
@@ -91,6 +116,16 @@ public String getCustomUserAgent() {
91116
return customUserAgent;
92117
}
93118

119+
/**
120+
* Returns the additional headers to be included in all requests.
121+
*
122+
* @return A map of custom headers (may be empty if none are set).
123+
* @since 9.2.0
124+
*/
125+
public Map<String, String> getCustomHeaders() {
126+
return customHeaders;
127+
}
128+
94129
/**
95130
* Returns the proxy URL to use for the underlying HTTP client configuration.
96131
*
@@ -125,6 +160,7 @@ public static Builder builder() {
125160
public static final class Builder {
126161
private int timeoutMillis = 60_000;
127162
private URI proxy;
163+
private Map<String, String> customHeaders = new LinkedHashMap<>(4);
128164
private Function<ApiRegion, String> regionalUriGetter = region -> "https://"+region+".vonage.com";
129165
private String customUserAgent,
130166
apiBaseUri = DEFAULT_API_BASE_URI,
@@ -187,6 +223,20 @@ public Builder proxy(URI proxy) {
187223
return this;
188224
}
189225

226+
/**
227+
* Add a custom HTTP header to all requests made with this client.
228+
*
229+
* @param name The header name.
230+
* @param value The header value.
231+
*
232+
* @return This builder.
233+
* @since 9.2.0
234+
*/
235+
public Builder addRequestHeader(String name, String value) {
236+
customHeaders.put(name, value);
237+
return this;
238+
}
239+
190240
/**
191241
* Replaces the URI used in "api" endpoints.
192242
*
@@ -240,12 +290,11 @@ public Builder videoBaseUri(String videoBaseUri) {
240290
*/
241291
public Builder baseUri(String baseUri) {
242292
String sanitizedUri = sanitizeUri(baseUri);
243-
regionalUriGetter(region -> sanitizedUri.replace("://", "://" + region + '.'));
244293
apiBaseUri = sanitizedUri;
245294
restBaseUri = sanitizedUri;
246295
apiEuBaseUri = sanitizedUri;
247296
videoBaseUri = sanitizedUri;
248-
return this;
297+
return regionalUriGetter(region -> sanitizedUri.replace("://", "://" + region + '.'));
249298
}
250299

251300
/**

src/main/java/com/vonage/client/HttpWrapper.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
public class HttpWrapper {
3838
private static final String
3939
CLIENT_NAME = "vonage-java-sdk",
40-
CLIENT_VERSION = "9.1.0",
40+
CLIENT_VERSION = "9.2.0",
4141
JAVA_VERSION = System.getProperty("java.version"),
4242
USER_AGENT = String.format("%s/%s java/%s", CLIENT_NAME, CLIENT_VERSION, JAVA_VERSION);
4343

@@ -54,34 +54,68 @@ public class HttpWrapper {
5454
*
5555
* @since 9.0.0
5656
*/
57-
HttpWrapper(HttpConfig httpConfig, AuthCollection authCollection, HttpClient httpClient) {
57+
public HttpWrapper(HttpConfig httpConfig, AuthCollection authCollection, HttpClient httpClient) {
5858
this.authCollection = authCollection;
5959
this.httpConfig = httpConfig;
6060
this.httpClient = httpClient instanceof CloseableHttpClient ?
6161
(CloseableHttpClient) httpClient : createHttpClient();
6262
}
6363

64+
/**
65+
* Creates a new instance of the HttpWrapper class.
66+
*
67+
* @param httpConfig The HTTP configuration settings to use.
68+
* @param authCollection The authentication settings to use.
69+
*/
6470
public HttpWrapper(HttpConfig httpConfig, AuthCollection authCollection) {
6571
this(httpConfig, authCollection, null);
6672
}
6773

74+
/**
75+
* Creates a new instance of the HttpWrapper class.
76+
*
77+
* @param authCollection The authentication settings to use.
78+
*/
6879
public HttpWrapper(AuthCollection authCollection) {
6980
this(HttpConfig.builder().build(), authCollection);
7081
}
7182

83+
/**
84+
* Creates a new instance of the HttpWrapper class.
85+
*
86+
* @param authMethods The authentication methods to use.
87+
*/
7288
public HttpWrapper(AuthMethod... authMethods) {
7389
this(HttpConfig.builder().build(), authMethods);
7490
}
7591

92+
/**
93+
* Creates a new instance of the HttpWrapper class.
94+
*
95+
* @param httpConfig The HTTP configuration settings to use.
96+
* @param authMethods The authentication methods to use.
97+
*/
7698
public HttpWrapper(HttpConfig httpConfig, AuthMethod... authMethods) {
7799
this(httpConfig, new AuthCollection(authMethods));
78100
}
79101

80-
void setHttpClient(CloseableHttpClient httpClient) {
102+
/**
103+
* Sets the HTTP client to be used by the SDK.
104+
*
105+
* @param httpClient The custom HTTP client instance to use.
106+
* @deprecated Please use {@linkplain HttpConfig} options where possible.
107+
*/
108+
@Deprecated
109+
public void setHttpClient(CloseableHttpClient httpClient) {
81110
this.httpClient = httpClient;
82111
}
83112

84-
void setHttpConfig(HttpConfig httpConfig) {
113+
/**
114+
* Sets the HTTP configuration options for the client.
115+
*
116+
* @param httpConfig The HTTP configuration settings to use.
117+
*/
118+
public void setHttpConfig(HttpConfig httpConfig) {
85119
this.httpConfig = httpConfig;
86120
}
87121

src/test/java/com/vonage/client/AbstractMethodTest.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
import com.vonage.client.auth.*;
2121
import com.vonage.client.auth.hashutils.HashType;
2222
import org.apache.commons.codec.binary.Base64;
23-
import org.apache.http.*;
23+
import org.apache.http.HttpEntity;
24+
import org.apache.http.HttpEntityEnclosingRequest;
25+
import org.apache.http.HttpResponse;
26+
import org.apache.http.ProtocolVersion;
2427
import org.apache.http.client.methods.CloseableHttpResponse;
2528
import org.apache.http.client.methods.HttpUriRequest;
2629
import org.apache.http.client.methods.RequestBuilder;
@@ -34,7 +37,10 @@
3437
import org.junit.jupiter.api.*;
3538
import org.mockito.ArgumentCaptor;
3639
import static org.mockito.Mockito.*;
37-
import java.io.*;
40+
import java.io.BufferedReader;
41+
import java.io.IOException;
42+
import java.io.InputStreamReader;
43+
import java.io.OutputStream;
3844
import java.net.InetSocketAddress;
3945
import java.net.SocketTimeoutException;
4046
import java.nio.charset.StandardCharsets;
@@ -119,6 +125,9 @@ public void setUp() throws Exception {
119125
AuthCollection mockAuthMethods = mock(AuthCollection.class);
120126
mockAuthMethod = mock(AuthMethod.class);
121127
mockHttpClient = mock(CloseableHttpClient.class);
128+
var httpConfig = HttpConfig.builder().build();
129+
when(mockWrapper.getHttpConfig()).thenReturn(httpConfig);
130+
when(mockAuthMethod.getSortKey()).thenReturn(0);
122131
when(mockAuthMethods.getAcceptableAuthMethod(any())).thenReturn(mockAuthMethod);
123132
when(mockWrapper.getHttpClient()).thenReturn(mockHttpClient);
124133
when(mockHttpClient.execute(any(HttpUriRequest.class))).thenReturn(basicResponse);
@@ -159,6 +168,22 @@ public void testExecuteHeaders() throws Exception {
159168
assertEquals(expected, result);
160169
}
161170

171+
@Test
172+
public void testCustomHeaderDoesNotOverrideUserAgent() {
173+
var overridenUa = "my-custom-agent Should not be possible";
174+
var cm = new ConcreteMethod(new HttpWrapper(HttpConfig.builder()
175+
.addRequestHeader("X-Correlation-Id", "aaaa-bbbb-cccc-dddd")
176+
.addRequestHeader("User-Agent", overridenUa)
177+
.build(), new NoAuthMethod()
178+
));
179+
var request = cm.createFullHttpRequest("https://example.com");
180+
var userAgentHeaders = request.getHeaders("User-Agent");
181+
assertNotNull(userAgentHeaders);
182+
assertEquals(1, userAgentHeaders.length);
183+
assertNotEquals(overridenUa, userAgentHeaders[0].getValue());
184+
assertEquals("aaaa-bbbb-cccc-dddd", request.getFirstHeader("X-Correlation-Id").getValue());
185+
}
186+
162187
@Test
163188
public void testGetAuthMethod() {
164189
ConcreteMethod method = new ConcreteMethod(mockWrapper);

0 commit comments

Comments
 (0)