Skip to content

Commit 297802d

Browse files
authored
feat: add new Options to allow per method header values (#2941)
Add new "Option"s for those methods which already have option types to allow providing an ImmutableMap<String, String> to be applied as extra headers to all requests sent as part of that operation. If an operation has multiple sources of input Options (rewrite) the "first" (i.e. source option) will be the one added to the request. The following resources do not have "Option"s and therefor do not have extra headers support at this time: * Acl * DefaultAcl * ServiceAccount * Notification
1 parent cc4c7f4 commit 297802d

21 files changed

+1913
-238
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.cloud.storage.spi.v1.StorageRpc;
3434
import com.google.common.annotations.VisibleForTesting;
3535
import com.google.common.base.MoreObjects;
36+
import com.google.common.collect.ImmutableMap;
3637
import com.google.common.hash.HashFunction;
3738
import com.google.common.hash.Hashing;
3839
import com.google.common.io.BaseEncoding;
@@ -51,8 +52,8 @@
5152
import java.nio.channels.ReadableByteChannel;
5253
import java.nio.channels.ScatteringByteChannel;
5354
import java.util.List;
54-
import java.util.Locale;
5555
import java.util.Map;
56+
import java.util.Map.Entry;
5657
import java.util.function.Function;
5758
import javax.annotation.concurrent.Immutable;
5859
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -273,6 +274,14 @@ static Get createGetRequest(
273274
"x-goog-encryption-key-sha256",
274275
base64.encode(hashFunction.hashBytes(base64.decode(key)).asBytes()));
275276
});
277+
ifNonNull(
278+
options.get(StorageRpc.Option.EXTRA_HEADERS),
279+
ApiaryUnbufferedReadableByteChannel::cast,
280+
(ImmutableMap<String, String> extraHeaders) -> {
281+
for (Entry<String, String> e : extraHeaders.entrySet()) {
282+
headers.set(e.getKey(), e.getValue());
283+
}
284+
});
276285

277286
// gzip handling is performed upstream of here. Ensure we always get the raw input stream from
278287
// the request
@@ -302,7 +311,7 @@ private static String getHeaderValue(@NonNull HttpHeaders headers, @NonNull Stri
302311
if (list.isEmpty()) {
303312
return null;
304313
} else {
305-
return list.get(0).trim().toLowerCase(Locale.ENGLISH);
314+
return Utils.headerNameToLowerCase(list.get(0).trim());
306315
}
307316
} else if (o instanceof String) {
308317
return (String) o;

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,10 @@ public CopyWriter copy(CopyRequest copyRequest) {
650650
Opts<ObjectTargetOpt> dstOpts =
651651
Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts);
652652

653-
Mapper<RewriteObjectRequest.Builder> mapper =
653+
Mapper<RewriteObjectRequest.Builder> requestBuilderMapper =
654654
srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest());
655+
Mapper<GrpcCallContext> grpcCallContextMapper =
656+
srcOpts.grpcMetadataMapper().andThen(dstOpts.grpcMetadataMapper());
655657

656658
Object srcProto = codecs.blobId().encode(src);
657659
Object dstProto = codecs.blobInfo().encode(dst);
@@ -686,9 +688,8 @@ public CopyWriter copy(CopyRequest copyRequest) {
686688
b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB);
687689
}
688690

689-
RewriteObjectRequest req = mapper.apply(b).build();
690-
GrpcCallContext grpcCallContext =
691-
srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
691+
RewriteObjectRequest req = requestBuilderMapper.apply(b).build();
692+
GrpcCallContext grpcCallContext = grpcCallContextMapper.apply(GrpcCallContext.createDefault());
692693
UnaryCallable<RewriteObjectRequest, RewriteResponse> callable =
693694
storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext);
694695
GrpcCallContext retryContext = Retrying.newCallContext();
@@ -733,7 +734,7 @@ public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption..
733734
public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) {
734735
Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts);
735736
ReadObjectRequest request = getReadObjectRequest(blob, opts);
736-
GrpcCallContext grpcCallContext = Retrying.newCallContext();
737+
GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(Retrying.newCallContext());
737738

738739
return new GrpcBlobReadChannel(
739740
storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext),

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
import java.util.IdentityHashMap;
9797
import java.util.Iterator;
9898
import java.util.List;
99-
import java.util.Locale;
10099
import java.util.Map;
101100
import java.util.Map.Entry;
102101
import java.util.Objects;
@@ -161,6 +160,11 @@ StorageSettings getStorageSettings() throws IOException {
161160
return resolveSettingsAndOpts().x();
162161
}
163162

163+
@InternalApi
164+
GrpcInterceptorProvider getGrpcInterceptorProvider() {
165+
return grpcInterceptorProvider;
166+
}
167+
164168
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
165169
in.defaultReadObject();
166170
this.openTelemetry = HttpStorageOptions.getDefaultInstance().getOpenTelemetry();
@@ -225,7 +229,7 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
225229
Map<String, List<String>> requestMetadata = credentials.getRequestMetadata(uri);
226230
for (Entry<String, List<String>> e : requestMetadata.entrySet()) {
227231
String key = e.getKey();
228-
if ("x-goog-user-project".equals(key.trim().toLowerCase(Locale.ENGLISH))) {
232+
if ("x-goog-user-project".equals(Utils.headerNameToLowerCase(key.trim()))) {
229233
List<String> value = e.getValue();
230234
if (!value.isEmpty()) {
231235
foundQuotaProject = true;

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,13 @@ final class JsonResumableSession {
5454
* have the concept of nested retry handling.
5555
*/
5656
ResumableOperationResult<@Nullable StorageObject> query() {
57-
return new JsonResumableSessionQueryTask(context, resumableWrite.getUploadId()).call();
57+
return new JsonResumableSessionQueryTask(context, resumableWrite).call();
5858
}
5959

6060
ResumableOperationResult<@Nullable StorageObject> put(
6161
RewindableContent content, HttpContentRange contentRange) {
6262
JsonResumableSessionPutTask task =
63-
new JsonResumableSessionPutTask(
64-
context, resumableWrite.getUploadId(), content, contentRange);
63+
new JsonResumableSessionPutTask(context, resumableWrite, content, contentRange);
6564
HttpRpcContext httpRpcContext = HttpRpcContext.getInstance();
6665
try {
6766
httpRpcContext.newInvocationId();

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java

+11-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.storage;
1818

1919
import com.google.api.client.http.GenericUrl;
20+
import com.google.api.client.http.HttpHeaders;
2021
import com.google.api.client.http.HttpRequest;
2122
import com.google.api.client.http.HttpResponse;
2223
import com.google.api.client.http.HttpResponseException;
@@ -31,14 +32,15 @@
3132
import java.io.IOException;
3233
import java.math.BigInteger;
3334
import java.util.Locale;
35+
import java.util.Map.Entry;
3436
import java.util.concurrent.Callable;
3537
import org.checkerframework.checker.nullness.qual.Nullable;
3638

3739
final class JsonResumableSessionPutTask
3840
implements Callable<ResumableOperationResult<@Nullable StorageObject>> {
3941

4042
private final HttpClientContext context;
41-
private final String uploadId;
43+
private final JsonResumableWrite jsonResumableWrite;
4244
private final RewindableContent content;
4345
private final HttpContentRange originalContentRange;
4446

@@ -47,11 +49,11 @@ final class JsonResumableSessionPutTask
4749
@VisibleForTesting
4850
JsonResumableSessionPutTask(
4951
HttpClientContext httpClientContext,
50-
String uploadId,
52+
JsonResumableWrite jsonResumableWrite,
5153
RewindableContent content,
5254
HttpContentRange originalContentRange) {
5355
this.context = httpClientContext;
54-
this.uploadId = uploadId;
56+
this.jsonResumableWrite = jsonResumableWrite;
5557
this.content = content;
5658
this.originalContentRange = originalContentRange;
5759
this.contentRange = originalContentRange;
@@ -87,13 +89,18 @@ public void rewindTo(long offset) {
8789
boolean success = false;
8890
boolean finalizing = originalContentRange.isFinalizing();
8991

92+
String uploadId = jsonResumableWrite.getUploadId();
9093
HttpRequest req =
9194
context
9295
.getRequestFactory()
9396
.buildPutRequest(new GenericUrl(uploadId), content)
9497
.setParser(context.getObjectParser());
9598
req.setThrowExceptionOnExecuteError(false);
96-
req.getHeaders().setContentRange(contentRange.getHeaderValue());
99+
HttpHeaders headers = req.getHeaders();
100+
headers.setContentRange(contentRange.getHeaderValue());
101+
for (Entry<String, String> e : jsonResumableWrite.getExtraHeaders().entrySet()) {
102+
headers.set(e.getKey(), e.getValue());
103+
}
97104

98105
HttpResponse response = null;
99106
try {

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java

+11-4
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,44 @@
2020

2121
import com.google.api.client.http.EmptyContent;
2222
import com.google.api.client.http.GenericUrl;
23+
import com.google.api.client.http.HttpHeaders;
2324
import com.google.api.client.http.HttpRequest;
2425
import com.google.api.client.http.HttpResponse;
2526
import com.google.api.client.http.HttpResponseException;
2627
import com.google.api.services.storage.model.StorageObject;
2728
import java.io.IOException;
2829
import java.math.BigInteger;
2930
import java.util.Locale;
31+
import java.util.Map.Entry;
3032
import java.util.concurrent.Callable;
3133
import org.checkerframework.checker.nullness.qual.Nullable;
3234

3335
final class JsonResumableSessionQueryTask
3436
implements Callable<ResumableOperationResult<@Nullable StorageObject>> {
3537

3638
private final HttpClientContext context;
37-
private final String uploadId;
39+
private final JsonResumableWrite jsonResumableWrite;
3840

39-
JsonResumableSessionQueryTask(HttpClientContext context, String uploadId) {
41+
JsonResumableSessionQueryTask(HttpClientContext context, JsonResumableWrite jsonResumableWrite) {
4042
this.context = context;
41-
this.uploadId = uploadId;
43+
this.jsonResumableWrite = jsonResumableWrite;
4244
}
4345

4446
public ResumableOperationResult<@Nullable StorageObject> call() {
4547
HttpResponse response = null;
48+
String uploadId = jsonResumableWrite.getUploadId();
4649
try {
4750
HttpRequest req =
4851
context
4952
.getRequestFactory()
5053
.buildPutRequest(new GenericUrl(uploadId), new EmptyContent())
5154
.setParser(context.getObjectParser());
5255
req.setThrowExceptionOnExecuteError(false);
53-
req.getHeaders().setContentRange(HttpContentRange.query().getHeaderValue());
56+
HttpHeaders headers = req.getHeaders();
57+
headers.setContentRange(HttpContentRange.query().getHeaderValue());
58+
for (Entry<String, String> e : jsonResumableWrite.getExtraHeaders().entrySet()) {
59+
headers.set(e.getKey(), e.getValue());
60+
}
5461

5562
response = req.execute();
5663

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java

+11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.api.services.storage.model.StorageObject;
2222
import com.google.cloud.storage.spi.v1.StorageRpc;
2323
import com.google.common.base.MoreObjects;
24+
import com.google.common.collect.ImmutableMap;
2425
import com.google.gson.Gson;
2526
import com.google.gson.stream.JsonReader;
2627
import java.io.IOException;
@@ -60,6 +61,16 @@ private JsonResumableWrite(
6061
this.beginOffset = beginOffset;
6162
}
6263

64+
ImmutableMap<String, String> getExtraHeaders() {
65+
if (options != null) {
66+
Object tmp = options.get(StorageRpc.Option.EXTRA_HEADERS);
67+
if (tmp != null) {
68+
return (ImmutableMap<String, String>) tmp;
69+
}
70+
}
71+
return ImmutableMap.of();
72+
}
73+
6374
public @NonNull String getUploadId() {
6475
return uploadId;
6576
}

google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import java.io.IOException;
3636
import java.io.InputStreamReader;
3737
import java.util.List;
38-
import java.util.Locale;
3938
import java.util.Map;
4039
import java.util.function.Consumer;
4140
import java.util.function.Predicate;
@@ -295,12 +294,12 @@ static boolean isContinue(int code) {
295294
// The header names from HttpHeaders are lower cased, define some utility methods to create
296295
// predicates where we can specify values ignoring case
297296
private static Predicate<String> matches(String expected) {
298-
String lower = expected.toLowerCase(Locale.US);
297+
String lower = Utils.headerNameToLowerCase(expected);
299298
return lower::equals;
300299
}
301300

302301
private static Predicate<String> startsWith(String prefix) {
303-
String lower = prefix.toLowerCase(Locale.US);
302+
String lower = Utils.headerNameToLowerCase(prefix);
304303
return s -> s.startsWith(lower);
305304
}
306305

0 commit comments

Comments
 (0)