Skip to content

Commit 3de7e2a

Browse files
authored
fix: allows user-agent header with header provider (#871)
* fix: allows user-agent header with header provider A bug was introduced, where if the caller tried to set a custom user agent with a header provider an exception would be thrown (for duplicate keys). Here, we merge the user agent set by the client along with the one set by the library, instead of throwing such exception. * test: adds test for default user agent Tests if the default user agent is present in the user-agent header set in the GapicSpannerRpc class.
1 parent ab14a5e commit 3de7e2a

File tree

2 files changed

+63
-12
lines changed

2 files changed

+63
-12
lines changed

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

Lines changed: 17 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);
318312
final HeaderProvider headerProviderWithUserAgent =
319-
FixedHeaderProvider.create(headersWithUserAgent);
313+
headerProviderWithUserAgentFrom(mergedHeaderProvider);
314+
320315
this.metadataProvider =
321316
SpannerMetadataProvider.create(
322317
headerProviderWithUserAgent.getHeaders(),
@@ -494,6 +489,16 @@ 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+
final String userAgent = headersWithUserAgent.get(USER_AGENT_KEY);
495+
headersWithUserAgent.put(
496+
USER_AGENT_KEY,
497+
userAgent == null ? DEFAULT_USER_AGENT : userAgent + " " + DEFAULT_USER_AGENT);
498+
499+
return FixedHeaderProvider.create(headersWithUserAgent);
500+
}
501+
497502
private static void checkEmulatorConnection(
498503
SpannerOptions options,
499504
TransportChannelProvider channelProvider,

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

Lines changed: 46 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,46 @@ 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+
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+
@Test
527+
public void testCustomUserAgent() {
528+
final HeaderProvider userAgentHeaderProvider =
529+
new HeaderProvider() {
530+
@Override
531+
public Map<String, String> getHeaders() {
532+
final Map<String, String> headers = new HashMap<>();
533+
headers.put("user-agent", "test-agent");
534+
return headers;
535+
}
536+
};
537+
final SpannerOptions options =
538+
createSpannerOptions().toBuilder().setHeaderProvider(userAgentHeaderProvider).build();
539+
final Spanner spanner = options.getService();
540+
final DatabaseClient databaseClient =
541+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
542+
543+
try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
544+
rs.next();
545+
}
546+
547+
assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER)))
548+
.contains("test-agent " + defaultUserAgent);
549+
}
550+
505551
@SuppressWarnings("rawtypes")
506552
private SpannerOptions createSpannerOptions() {
507553
String endpoint = address.getHostString() + ":" + server.getPort();

0 commit comments

Comments
 (0)