Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

Commit 266329e

Browse files
feat: Allow user-agents to be specified by both internal headers and user headers (#1190)
* feat: Allow user-agents to be specified by both internal headers and user headers This is primarily meant to address the googleapis/java-bigtable#404 (review). Where both the hbase adapter and the core client needs to set the UserAgent. To reconcile the conflict this PR takes grpc's approach and concatenates the values. The reason this needs to happen now is that for DirectPath we need to ship clients that send a UserAgent, however Bigtable needs to keep stats to differentiate client usage. * remove stale code
1 parent 287cada commit 266329e

File tree

2 files changed

+99
-18
lines changed

2 files changed

+99
-18
lines changed

‎gax/src/main/java/com/google/api/gax/rpc/ClientContext.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@
4242
import com.google.auto.value.AutoValue;
4343
import com.google.common.collect.ImmutableList;
4444
import com.google.common.collect.ImmutableMap;
45+
import com.google.common.collect.Sets;
4546
import java.io.IOException;
4647
import java.util.Collections;
48+
import java.util.HashMap;
4749
import java.util.List;
4850
import java.util.Map;
51+
import java.util.Set;
4952
import java.util.concurrent.Executor;
5053
import java.util.concurrent.Executors;
5154
import java.util.concurrent.ScheduledExecutorService;
@@ -222,27 +225,33 @@ public static ClientContext create(StubSettings settings) throws IOException {
222225
* Project Id.
223226
*/
224227
private static Map<String, String> getHeadersFromSettings(StubSettings settings) {
225-
ImmutableMap.Builder<String, String> headersBuilder = ImmutableMap.builder();
226-
if (settings.getQuotaProjectId() != null) {
227-
headersBuilder.put(QUOTA_PROJECT_ID_HEADER_KEY, settings.getQuotaProjectId());
228-
for (Map.Entry<String, String> entry : settings.getHeaderProvider().getHeaders().entrySet()) {
229-
if (entry.getKey().equals(QUOTA_PROJECT_ID_HEADER_KEY)) {
230-
continue;
231-
}
232-
headersBuilder.put(entry);
228+
// Resolve conflicts when merging headers from multiple sources
229+
Map<String, String> userHeaders = settings.getHeaderProvider().getHeaders();
230+
Map<String, String> internalHeaders = settings.getInternalHeaderProvider().getHeaders();
231+
Map<String, String> conflictResolution = new HashMap<>();
232+
233+
Set<String> conflicts = Sets.intersection(userHeaders.keySet(), internalHeaders.keySet());
234+
for (String key : conflicts) {
235+
if ("user-agent".equals(key)) {
236+
conflictResolution.put(key, userHeaders.get(key) + " " + internalHeaders.get(key));
237+
continue;
233238
}
234-
for (Map.Entry<String, String> entry :
235-
settings.getInternalHeaderProvider().getHeaders().entrySet()) {
236-
if (entry.getKey().equals(QUOTA_PROJECT_ID_HEADER_KEY)) {
237-
continue;
238-
}
239-
headersBuilder.put(entry);
239+
// Backwards compat: quota project id can conflict if its overriden in settings
240+
if (QUOTA_PROJECT_ID_HEADER_KEY.equals(key) && settings.getQuotaProjectId() != null) {
241+
continue;
240242
}
241-
} else {
242-
headersBuilder.putAll(settings.getHeaderProvider().getHeaders());
243-
headersBuilder.putAll(settings.getInternalHeaderProvider().getHeaders());
243+
throw new IllegalArgumentException("Header provider can't override the header: " + key);
244+
}
245+
if (settings.getQuotaProjectId() != null) {
246+
conflictResolution.put(QUOTA_PROJECT_ID_HEADER_KEY, settings.getQuotaProjectId());
244247
}
245-
return headersBuilder.build();
248+
249+
Map<String, String> effectiveHeaders = new HashMap<>();
250+
effectiveHeaders.putAll(internalHeaders);
251+
effectiveHeaders.putAll(userHeaders);
252+
effectiveHeaders.putAll(conflictResolution);
253+
254+
return ImmutableMap.copyOf(effectiveHeaders);
246255
}
247256

248257
@AutoValue.Builder

‎gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.google.api.gax.core.CredentialsProvider;
3737
import com.google.api.gax.core.ExecutorProvider;
3838
import com.google.api.gax.core.FixedCredentialsProvider;
39+
import com.google.api.gax.core.FixedExecutorProvider;
3940
import com.google.api.gax.rpc.testing.FakeChannel;
4041
import com.google.api.gax.rpc.testing.FakeClientSettings;
4142
import com.google.api.gax.rpc.testing.FakeTransportChannel;
@@ -526,4 +527,75 @@ public Credentials getCredentials() throws IOException {
526527
ClientContext clientContext = ClientContext.create(builder.build());
527528
assertThat(clientContext.getCredentials().getRequestMetadata(null)).isEqualTo(metaData);
528529
}
530+
531+
@Test
532+
public void testUserAgentInternalOnly() throws Exception {
533+
TransportChannelProvider transportChannelProvider =
534+
new FakeTransportProvider(
535+
FakeTransportChannel.create(new FakeChannel()), null, true, null, null);
536+
537+
ClientSettings.Builder builder =
538+
new FakeClientSettings.Builder()
539+
.setExecutorProvider(
540+
FixedExecutorProvider.create(Mockito.mock(ScheduledExecutorService.class)))
541+
.setTransportChannelProvider(transportChannelProvider)
542+
.setCredentialsProvider(
543+
FixedCredentialsProvider.create(Mockito.mock(GoogleCredentials.class)));
544+
545+
builder.setInternalHeaderProvider(FixedHeaderProvider.create("user-agent", "internal-agent"));
546+
547+
ClientContext clientContext = ClientContext.create(builder.build());
548+
FakeTransportChannel transportChannel =
549+
(FakeTransportChannel) clientContext.getTransportChannel();
550+
551+
assertThat(transportChannel.getHeaders()).containsEntry("user-agent", "internal-agent");
552+
}
553+
554+
@Test
555+
public void testUserAgentExternalOnly() throws Exception {
556+
TransportChannelProvider transportChannelProvider =
557+
new FakeTransportProvider(
558+
FakeTransportChannel.create(new FakeChannel()), null, true, null, null);
559+
560+
ClientSettings.Builder builder =
561+
new FakeClientSettings.Builder()
562+
.setExecutorProvider(
563+
FixedExecutorProvider.create(Mockito.mock(ScheduledExecutorService.class)))
564+
.setTransportChannelProvider(transportChannelProvider)
565+
.setCredentialsProvider(
566+
FixedCredentialsProvider.create(Mockito.mock(GoogleCredentials.class)));
567+
568+
builder.setHeaderProvider(FixedHeaderProvider.create("user-agent", "user-supplied-agent"));
569+
570+
ClientContext clientContext = ClientContext.create(builder.build());
571+
FakeTransportChannel transportChannel =
572+
(FakeTransportChannel) clientContext.getTransportChannel();
573+
574+
assertThat(transportChannel.getHeaders()).containsEntry("user-agent", "user-supplied-agent");
575+
}
576+
577+
@Test
578+
public void testUserAgentConcat() throws Exception {
579+
TransportChannelProvider transportChannelProvider =
580+
new FakeTransportProvider(
581+
FakeTransportChannel.create(new FakeChannel()), null, true, null, null);
582+
583+
ClientSettings.Builder builder =
584+
new FakeClientSettings.Builder()
585+
.setExecutorProvider(
586+
FixedExecutorProvider.create(Mockito.mock(ScheduledExecutorService.class)))
587+
.setTransportChannelProvider(transportChannelProvider)
588+
.setCredentialsProvider(
589+
FixedCredentialsProvider.create(Mockito.mock(GoogleCredentials.class)));
590+
591+
builder.setHeaderProvider(FixedHeaderProvider.create("user-agent", "user-supplied-agent"));
592+
builder.setInternalHeaderProvider(FixedHeaderProvider.create("user-agent", "internal-agent"));
593+
594+
ClientContext clientContext = ClientContext.create(builder.build());
595+
FakeTransportChannel transportChannel =
596+
(FakeTransportChannel) clientContext.getTransportChannel();
597+
598+
assertThat(transportChannel.getHeaders())
599+
.containsEntry("user-agent", "user-supplied-agent internal-agent");
600+
}
529601
}

0 commit comments

Comments
 (0)