Skip to content

Commit 9ddb266

Browse files
authored
fix: allows setting custom user agent in any case (#1129)
Allows setting a custom user agent in any case, instead of only in lower case.
1 parent 7d41ebd commit 9ddb266

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
import com.google.common.base.MoreObjects;
7878
import com.google.common.base.Preconditions;
7979
import com.google.common.collect.ImmutableList;
80-
import com.google.common.collect.ImmutableMap;
8180
import com.google.common.collect.ImmutableSet;
8281
import com.google.common.util.concurrent.RateLimiter;
8382
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -161,6 +160,7 @@
161160
import java.net.URLDecoder;
162161
import java.nio.charset.StandardCharsets;
163162
import java.util.Comparator;
163+
import java.util.HashMap;
164164
import java.util.LinkedList;
165165
import java.util.List;
166166
import java.util.Map;
@@ -244,6 +244,8 @@ private void awaitTermination() throws InterruptedException {
244244
private static final int GRPC_KEEPALIVE_SECONDS = 2 * 60;
245245
private static final String USER_AGENT_KEY = "user-agent";
246246
private static final String CLIENT_LIBRARY_LANGUAGE = "spanner-java";
247+
public static final String DEFAULT_USER_AGENT =
248+
CLIENT_LIBRARY_LANGUAGE + "/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class);
247249

248250
private final ManagedInstantiatingExecutorProvider executorProvider;
249251
private boolean rpcIsClosed;
@@ -305,18 +307,11 @@ public GapicSpannerRpc(final SpannerOptions options) {
305307
GaxGrpcProperties.getGrpcTokenName(), GaxGrpcProperties.getGrpcVersion())
306308
.build();
307309

308-
HeaderProvider mergedHeaderProvider = options.getMergedHeaderProvider(internalHeaderProvider);
309-
Map<String, String> headersWithUserAgent =
310-
ImmutableMap.<String, String>builder()
311-
.put(
312-
USER_AGENT_KEY,
313-
CLIENT_LIBRARY_LANGUAGE
314-
+ "/"
315-
+ GaxProperties.getLibraryVersion(GapicSpannerRpc.class))
316-
.putAll(mergedHeaderProvider.getHeaders())
317-
.build();
310+
final HeaderProvider mergedHeaderProvider =
311+
options.getMergedHeaderProvider(internalHeaderProvider);
312+
318313
final HeaderProvider headerProviderWithUserAgent =
319-
FixedHeaderProvider.create(headersWithUserAgent);
314+
headerProviderWithUserAgentFrom(mergedHeaderProvider);
320315
this.metadataProvider =
321316
SpannerMetadataProvider.create(
322317
headerProviderWithUserAgent.getHeaders(),
@@ -494,6 +489,24 @@ public <RequestT, ResponseT> UnaryCallable<RequestT, ResponseT> createUnaryCalla
494489
}
495490
}
496491

492+
private static HeaderProvider headerProviderWithUserAgentFrom(HeaderProvider headerProvider) {
493+
final Map<String, String> headersWithUserAgent = new HashMap<>(headerProvider.getHeaders());
494+
String userAgent = null;
495+
for (Map.Entry<String, String> entry : headersWithUserAgent.entrySet()) {
496+
if (entry.getKey().equalsIgnoreCase(USER_AGENT_KEY)) {
497+
userAgent = entry.getValue();
498+
headersWithUserAgent.remove(entry.getKey());
499+
break;
500+
}
501+
}
502+
503+
headersWithUserAgent.put(
504+
USER_AGENT_KEY,
505+
userAgent == null ? DEFAULT_USER_AGENT : userAgent + " " + DEFAULT_USER_AGENT);
506+
507+
return FixedHeaderProvider.create(headersWithUserAgent);
508+
}
509+
497510
private static void checkEmulatorConnection(
498511
SpannerOptions options,
499512
TransportChannelProvider channelProvider,

google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import static org.junit.Assume.assumeTrue;
2525

2626
import com.google.api.core.ApiFunction;
27+
import com.google.api.gax.core.GaxProperties;
2728
import com.google.api.gax.rpc.ApiCallContext;
29+
import com.google.api.gax.rpc.HeaderProvider;
2830
import com.google.auth.oauth2.AccessToken;
2931
import com.google.auth.oauth2.OAuth2Credentials;
3032
import com.google.cloud.spanner.DatabaseAdminClient;
@@ -151,6 +153,8 @@ public class GapicSpannerRpcTest {
151153
private Server server;
152154
private InetSocketAddress address;
153155
private final Map<SpannerRpc.Option, Object> optionsMap = new HashMap<>();
156+
private Metadata seenHeaders;
157+
private String defaultUserAgent;
154158

155159
@BeforeClass
156160
public static void checkNotEmulator() {
@@ -161,6 +165,7 @@ public static void checkNotEmulator() {
161165

162166
@Before
163167
public void startServer() throws IOException {
168+
defaultUserAgent = "spanner-java/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class);
164169
mockSpanner = new MockSpannerServiceImpl();
165170
mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions.
166171
mockSpanner.putStatementResult(StatementResult.query(SELECT1AND2, SELECT1_RESULTSET));
@@ -183,6 +188,7 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
183188
ServerCall<ReqT, RespT> call,
184189
Metadata headers,
185190
ServerCallHandler<ReqT, RespT> next) {
191+
seenHeaders = headers;
186192
String auth =
187193
headers.get(Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER));
188194
assertThat(auth).isEqualTo("Bearer " + VARIABLE_OAUTH_TOKEN);
@@ -502,6 +508,50 @@ public void testAdminRequestsLimitExceededRetryAlgorithm() {
502508
assertThat(alg.shouldRetry(new Exception("random exception"), null)).isFalse();
503509
}
504510

511+
@Test
512+
public void testDefaultUserAgent() {
513+
final SpannerOptions options = createSpannerOptions();
514+
try (final Spanner spanner = options.getService()) {
515+
final DatabaseClient databaseClient =
516+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
517+
518+
try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
519+
rs.next();
520+
}
521+
522+
assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER)))
523+
.contains(defaultUserAgent);
524+
}
525+
}
526+
527+
@Test
528+
public void testCustomUserAgent() {
529+
for (final String headerId : new String[] {"user-agent", "User-Agent", "USER-AGENT"}) {
530+
final HeaderProvider userAgentHeaderProvider =
531+
new HeaderProvider() {
532+
@Override
533+
public Map<String, String> getHeaders() {
534+
final Map<String, String> headers = new HashMap<>();
535+
headers.put(headerId, "test-agent");
536+
return headers;
537+
}
538+
};
539+
final SpannerOptions options =
540+
createSpannerOptions().toBuilder().setHeaderProvider(userAgentHeaderProvider).build();
541+
try (Spanner spanner = options.getService()) {
542+
final DatabaseClient databaseClient =
543+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
544+
545+
try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
546+
rs.next();
547+
}
548+
549+
assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER)))
550+
.contains("test-agent " + defaultUserAgent);
551+
}
552+
}
553+
}
554+
505555
@SuppressWarnings("rawtypes")
506556
private SpannerOptions createSpannerOptions() {
507557
String endpoint = address.getHostString() + ":" + server.getPort();

0 commit comments

Comments
 (0)