Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEVEXP-111: add User-Agent SDK header #21

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/resources/version.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
project.version=${project.version}
project.name=${project.name}
project.auxiliary_flag=
56 changes: 52 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,27 @@
/** 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_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 +62,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 +79,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 +152,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 +181,34 @@ 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",
System.getProperty("java.version"),
"Apache",
formatAuxiliaryFlag((String) versionProperties.get(PROJECT_AUXILIARY_FLAG)));
}

private String formatAuxiliaryFlag(String auxiliaryFlag) {

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

if (!StringUtil.isEmpty(auxiliaryFlag)) {
values.add(auxiliaryFlag);
}
return String.join(",", values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ public BaseBatch(
this.feedbackEnabled = feedbackEnabled;
}

public static <T> BatchBuilder<T> batchBuilder() {
return new BatchBuilder<>();
}

public Collection<String> getTo() {
return to;
}
Expand Down
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