Skip to content
Open
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
8 changes: 8 additions & 0 deletions client/secureclient/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ dependencies {

}

// Stamp the project version into the jar manifest so WebClientFactory can advertise the client
// version in the User-Agent header by reading Implementation-Version at runtime.
jar {
manifest {
attributes('Implementation-Version': project.version)
}
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
Expand All @@ -26,6 +27,12 @@ public abstract class WebClientFactory {
private static final String SESSION_ID = "session-id";

public static final String HTTP_HEADER_CLIENT_NAME = "X-Client-Name";
// Product token advertised in the User-Agent header so the server can observe the client version.
// The resulting header looks like "openhouse-java-client/1.5.2".
public static final String USER_AGENT_CLIENT_PRODUCT = "openhouse-java-client";
// Fallback when the client jar manifest carries no Implementation-Version (e.g. running from
// classes during local/dev/tests) and no explicit version was set.
private static final String CLIENT_VERSION_UNKNOWN = "unknown";
private static final int IN_MEMORY_BUFFER_SIZE = 20 * 1024 * 1024;
// The maximum number of connections per connection pool
private static final int MAX_CONNECTION_POOL_SIZE = 500;
Expand All @@ -45,6 +52,10 @@ public abstract class WebClientFactory {

@Setter private String clientName = null;

// When null, the version is resolved from the client jar manifest's Implementation-Version,
// falling back to {@link #CLIENT_VERSION_UNKNOWN}. Callers may set an explicit value to override.
@Setter private String clientVersion = null;

protected WebClientFactory() {
setStrategy();
}
Expand Down Expand Up @@ -159,6 +170,7 @@ private WebClient getWebClient(String baseUrl, HttpClient httpClient) {
WebClient.Builder webClientBuilder = createWebClientBuilder();
setSessionIdInWebClientHeader(webClientBuilder);
setClientNameInWebClientHeader(webClientBuilder);
setUserAgentInWebClientHeader(webClientBuilder);
return webClientBuilder
.baseUrl(baseUrl)
.clientConnector(new ReactorClientHttpConnector(httpClient))
Expand Down Expand Up @@ -209,6 +221,37 @@ private void setClientNameInWebClientHeader(WebClient.Builder webClientBuilder)
log.info("Client name: {}", clientName);
}

/**
* Set the User-Agent header on the webClient so the server can observe the client version on
* every request, e.g. "openhouse-java-client/1.5.2". The version is resolved via {@link
* #resolveClientVersion()}. Setting our own default User-Agent replaces the transport's default
* (e.g. "ReactorNetty/x.y.z").
*
* @param webClientBuilder
*/
private void setUserAgentInWebClientHeader(WebClient.Builder webClientBuilder) {
String userAgent = USER_AGENT_CLIENT_PRODUCT + "/" + resolveClientVersion();
webClientBuilder.defaultHeaders(h -> h.set(HttpHeaders.USER_AGENT, userAgent));
log.info("Client User-Agent: {}", userAgent);
}

/**
* Resolve the client version to advertise: an explicitly set {@link #clientVersion} takes
* precedence, otherwise the Implementation-Version stamped into this jar's manifest, otherwise
* {@link #CLIENT_VERSION_UNKNOWN}.
*
* @return the resolved client version, never null
*/
private String resolveClientVersion() {
if (!StringUtil.isNullOrEmpty(clientVersion)) {
return clientVersion;
}
String implementationVersion = WebClientFactory.class.getPackage().getImplementationVersion();
return StringUtil.isNullOrEmpty(implementationVersion)
? CLIENT_VERSION_UNKNOWN
: implementationVersion;
}

/**
* Returns custom connection provider
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,16 @@ public void testSetClientNameCalled() throws Exception {
.setClientName(clientNameCapture.capture());
assertEquals("trino", clientNameCapture.getValue());
}

@Test
public void testSetClientVersionCalled() throws Exception {
ArgumentCaptor<String> clientVersionCapture = ArgumentCaptor.forClass(String.class);

tablesApiClientFactorySpy.setClientVersion("1.2.3");
tablesApiClientFactorySpy.createApiClient(
"https://test.openhouse.com", "", tmpCert.getAbsolutePath());
Mockito.verify(tablesApiClientFactorySpy, Mockito.times(1))
.setClientVersion(clientVersionCapture.capture());
assertEquals("1.2.3", clientVersionCapture.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public class OpenHouseCatalog extends BaseMetastoreCatalog

public static final String CLIENT_NAME = "client-name";

public static final String CLIENT_VERSION = "client-version";

@Override
public void initialize(String name, Map<String, String> properties) {
this.name = name;
Expand All @@ -113,10 +115,12 @@ public void initialize(String name, Map<String, String> properties) {
String token = properties.getOrDefault(AUTH_TOKEN, null);
String httpConnectionStrategy = properties.getOrDefault(HTTP_CONNECTION_STRATEGY, null);
String clientName = properties.getOrDefault(CLIENT_NAME, null);
String clientVersion = properties.getOrDefault(CLIENT_VERSION, null);
try {
TablesApiClientFactory tablesApiClientFactory = TablesApiClientFactory.getInstance();
tablesApiClientFactory.setStrategy(HttpConnectionStrategy.fromString(httpConnectionStrategy));
tablesApiClientFactory.setClientName(clientName);
tablesApiClientFactory.setClientVersion(clientVersion);
if (properties.containsKey(CatalogProperties.APP_ID)) {
tablesApiClientFactory.setSessionId(properties.get(CatalogProperties.APP_ID));
}
Expand Down