Skip to content

Commit

Permalink
feat: Send SDK 'User-Agent' header
Browse files Browse the repository at this point in the history
  • Loading branch information
JPPortier committed Nov 29, 2023
1 parent fb1abb3 commit b98f335
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 4 deletions.
4 changes: 4 additions & 0 deletions client/resources/version.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project.version=${project.version}
project.name=${project.name}
project.auxiliary_flag=
project.java.version=${maven.compiler.source}
58 changes: 54 additions & 4 deletions client/src/main/com/sinch/sdk/SinchClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.sinch.sdk.auth.adapters.BasicAuthManager;
import com.sinch.sdk.auth.adapters.BearerAuthManager;
import com.sinch.sdk.core.http.HttpMapper;
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.domains.numbers.NumbersService;
import com.sinch.sdk.domains.sms.SMSService;
import com.sinch.sdk.http.HttpClientApache;
Expand All @@ -12,6 +13,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
Expand All @@ -22,14 +25,28 @@
/** Sinch Sdk Client implementation */
public class SinchClient {

private static final String DEFAULT_PROPERTIES_FILE_NAME = "/config-default.properties";
private static final String VERSION_PROPERTIES_FILE_NAME = "/version.properties";

private static final String OAUTH_URL_KEY = "oauth-url";
private static final String NUMBERS_SERVER_KEY = "numbers-server";
private static final String SMS_REGION_KEY = "sms-region";
private static final String SMS_SERVER_KEY = "sms-server";

private static final String PROJECT_NAME_KEY = "project.name";
private static final String PROJECT_VERSION_KEY = "project.version";
private static final String PROJECT_JAVA_VERSION_KEY = "project.java.version";
private static final String PROJECT_AUXILIARY_FLAG = "project.auxiliary_flag";

// sinch-sdk/{sdk_version} ({language}/{language_version}; {implementation_type};
// {auxiliary_flag})
private static final String SDK_USER_AGENT_HEADER = "User-Agent";
private static final String SDK_USER_AGENT_FORMAT = "sinch-sdk/%s (%s/%s; %s; %s)";
private static final Logger LOGGER = Logger.getLogger(SinchClient.class.getName());

private final Configuration configuration;
private final Properties versionProperties;

private NumbersService numbers;

private SMSService sms;
Expand All @@ -46,7 +63,7 @@ public SinchClient(Configuration configuration) {

Configuration.Builder builder = Configuration.builder(configuration);

Properties props = handleDefaultConfigurationFile();
Properties props = handlePropertiesFile(DEFAULT_PROPERTIES_FILE_NAME);
if (null == configuration.getOAuthUrl() && props.containsKey(OAUTH_URL_KEY)) {
builder.setOAuthUrl(props.getProperty(OAUTH_URL_KEY));
}
Expand All @@ -63,7 +80,13 @@ public SinchClient(Configuration configuration) {
checkConfiguration(newConfiguration);
this.configuration = newConfiguration;

LOGGER.fine("SinchClient started with projectId='" + configuration.getProjectId() + "'");
versionProperties = handlePropertiesFile(VERSION_PROPERTIES_FILE_NAME);
LOGGER.fine(
String.format(
"%s (%s) started with projectId '%s'",
versionProperties.getProperty(PROJECT_NAME_KEY),
versionProperties.getProperty(PROJECT_VERSION_KEY),
configuration.getProjectId()));
}

/**
Expand Down Expand Up @@ -130,10 +153,10 @@ private SMSService smsInit() {
return new com.sinch.sdk.domains.sms.adapters.SMSService(getConfiguration(), getHttpClient());
}

private Properties handleDefaultConfigurationFile() {
private Properties handlePropertiesFile(String fileName) {

Properties prop = new Properties();
try (InputStream is = this.getClass().getResourceAsStream("/config-default.properties")) {
try (InputStream is = this.getClass().getResourceAsStream(fileName)) {
prop.load(is);
} catch (IOException e) {
// NOOP
Expand All @@ -159,8 +182,35 @@ private HttpClientApache getHttpClient() {
// Avoid multiple and costly http client creation and reuse it for authManager
bearerAuthManager.setHttpClient(this.httpClient);

// set SDK User-Agent
String userAgent = formatSdkUserAgentHeader(versionProperties);
this.httpClient.setRequestHeaders(
Stream.of(new String[][] {{SDK_USER_AGENT_HEADER, userAgent}})
.collect(Collectors.toMap(data -> data[0], data -> data[1])));

LOGGER.fine("HTTP client loaded");
}
return this.httpClient;
}

private String formatSdkUserAgentHeader(Properties versionProperties) {
return String.format(
SDK_USER_AGENT_FORMAT,
versionProperties.get(PROJECT_VERSION_KEY),
"Java",
versionProperties.get(PROJECT_JAVA_VERSION_KEY),
"Apache",
formatAuxiliaryFlag((String) versionProperties.get(PROJECT_AUXILIARY_FLAG)));
}

private String formatAuxiliaryFlag(String auxiliaryFlag) {

Collection<String> values =
Arrays.asList(System.getProperty("java.vendor"), System.getProperty("java.version"));

if (!StringUtil.isEmpty(auxiliaryFlag)) {
values.add(auxiliaryFlag);
}
return String.join(",", values);
}
}
20 changes: 20 additions & 0 deletions client/src/main/com/sinch/sdk/http/HttpClientApache.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient {
private static final Logger LOGGER = Logger.getLogger(HttpClientApache.class.getName());
private static final String AUTHORIZATION_HEADER_KEYWORD = "Authorization";
private final Map<String, AuthManager> authManagers;

private Map<String, String> headersToBeAdded;
private CloseableHttpClient client;

public HttpClientApache(Map<String, AuthManager> authManagers) {
this.client = HttpClients.createDefault();
this.authManagers = authManagers;
}

public void setRequestHeaders(Map<String, String> headers) {
this.headersToBeAdded = headers;
}

private static HttpResponse processResponse(ClassicHttpResponse response) throws IOException {

int statusCode = response.getCode();
Expand Down Expand Up @@ -98,6 +104,9 @@ public HttpResponse invokeAPI(ServerConfiguration serverConfiguration, HttpReque
addCollectionHeader(requestBuilder, "Content-Type", contentType);
addCollectionHeader(requestBuilder, "Accept", accept);

addHeaders(requestBuilder, headerParams);
addHeaders(requestBuilder, headersToBeAdded);

addAuth(requestBuilder, authNames);

ClassicHttpRequest request = requestBuilder.build();
Expand Down Expand Up @@ -169,6 +178,17 @@ private void addCollectionHeader(
}
}

private void addHeaders(ClassicRequestBuilder requestBuilder, Map<String, String> headers) {

if (null == headers) {
return;
}
headers
.entrySet()
.iterator()
.forEachRemaining(f -> requestBuilder.setHeader(f.getKey(), f.getValue()));
}

private void addAuth(ClassicRequestBuilder requestBuilder, Collection<String> values) {
if (null == values || values.isEmpty()) {
return;
Expand Down
57 changes: 57 additions & 0 deletions client/src/test/java/com/sinch/sdk/SinchClientTestIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sinch.sdk;

import static org.assertj.core.api.Assertions.assertThat;

import com.adelean.inject.resources.junit.jupiter.TestWithResources;
import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.models.Configuration;
import java.io.IOException;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

@TestWithResources
class SinchClientTestIT extends BaseTest {
static MockWebServer mockBackEnd;
String serverUrl = String.format("http://localhost:%s", mockBackEnd.getPort());

Configuration configuration =
Configuration.builder()
.setKeyId("foo")
.setKeySecret("foo")
.setProjectId("foo")
.setNumbersUrl(serverUrl)
.build();

SinchClient sinchClient = new SinchClient(configuration);

@BeforeAll
static void classSetUp() throws IOException {
mockBackEnd = new MockWebServer();
mockBackEnd.start();
}

@AfterAll
static void tearDown() throws IOException {
mockBackEnd.shutdown();
}

@Test
void sdkUserAgent() throws InterruptedException {

mockBackEnd.enqueue(
new MockResponse().setBody("foo").addHeader("Content-Type", "application/json"));

try {
sinchClient.numbers().available().checkAvailability("foo");
} catch (ApiException ae) {
// noop
}
RecordedRequest recordedRequest = mockBackEnd.takeRequest();
String header = recordedRequest.getHeader("User-Agent");
assertThat(header).matches("^sinch-sdk/.* \\(Java/.*; Apache; .*\\)$");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,50 @@ void bearerAutoRefresh() throws InterruptedException {
header = recordedRequest.getHeader("Authorization");
assertEquals(header, "Bearer another token");
}

@Test
void httpRequestHeaders() throws InterruptedException {
String key = "My-OAS-Header-Key";
String value = "OAS Header Value";
Map<String, String> httpRequest =
Stream.of(new String[][] {{key, value}})
.collect(Collectors.toMap(data -> data[0], data -> data[1]));

mockBackEnd.enqueue(
new MockResponse().setBody("foo").addHeader("Content-Type", "application/json"));

try {
httpClient.invokeAPI(
new ServerConfiguration(String.format("%s/foo/", serverUrl)),
new HttpRequest("foo-path", HttpMethod.GET, null, null, httpRequest, null, null, null));
} catch (ApiException ae) {
// noop
}
RecordedRequest recordedRequest = mockBackEnd.takeRequest();
String header = recordedRequest.getHeader(key);
assertEquals(header, value);
}

@Test
void sdkHeaders() throws InterruptedException {
String key = "My-Sdk-Header-Key";
String value = "SDK Header Value";
httpClient.setRequestHeaders(
Stream.of(new String[][] {{key, value}})
.collect(Collectors.toMap(data -> data[0], data -> data[1])));

mockBackEnd.enqueue(
new MockResponse().setBody("foo").addHeader("Content-Type", "application/json"));

try {
httpClient.invokeAPI(
new ServerConfiguration(String.format("%s/foo/", serverUrl)),
new HttpRequest("foo-path", HttpMethod.GET, null, null, null, null, null, null));
} catch (ApiException ae) {
// noop
}
RecordedRequest recordedRequest = mockBackEnd.takeRequest();
String header = recordedRequest.getHeader(key);
assertEquals(header, value);
}
}
8 changes: 8 additions & 0 deletions core/src/main/com/sinch/sdk/core/http/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.core.models.ServerConfiguration;
import java.util.Map;

public interface HttpClient extends AutoCloseable {

boolean isClosed();

void close() throws Exception;

/**
* Register a set of headers to be added onto requests
*
* @param headers Map of key/value headers to be added
*/
void setRequestHeaders(Map<String, String> headers);

HttpResponse invokeAPI(ServerConfiguration serverConfiguration, HttpRequest request)
throws ApiException;
}
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@
</properties>

<build>
<resources>
<resource>
<directory>client/resources</directory>
<filtering>true</filtering>
</resource>
</resources>

<pluginManagement>
<plugins>
Expand Down Expand Up @@ -374,6 +380,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.5</version>
</dependency>

</dependencies>

<profiles>
Expand Down

0 comments on commit b98f335

Please sign in to comment.