Skip to content

Commit 98521f2

Browse files
committed
fix: a resumable session without a Range header should be interpreted as 0 length
According to https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check a 308 response that does not contain a Range header should interpret as GCS having received no data. Include x-goog-gcs-idempotency-token in Json Resumable upload debug context
1 parent c89d275 commit 98521f2

File tree

4 files changed

+27
-11
lines changed

4 files changed

+27
-11
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ enum JsonResumableSessionFailureScenario {
6565
BaseServiceException.UNKNOWN_CODE,
6666
"dataLoss",
6767
"Client side data loss detected. Bytes acked is more than client sent."),
68-
SCENARIO_9(503, "backendNotConnected", "Ack less than bytes sent"),
69-
QUERY_SCENARIO_1(503, "", "Missing Range header in response");
68+
SCENARIO_9(503, "backendNotConnected", "Ack less than bytes sent");
7069

7170
private static final String PREFIX_I = "\t|< ";
7271
private static final String PREFIX_O = "\t|> ";
@@ -79,6 +78,7 @@ enum JsonResumableSessionFailureScenario {
7978
.or(matches("Content-Type"))
8079
.or(matches("Range"))
8180
.or(startsWith("X-Goog-Stored-"))
81+
.or(matches("X-Goog-GCS-Idempotency-Token"))
8282
.or(matches("X-GUploader-UploadID"));
8383

8484
private static final Predicate<Map.Entry<String, ?>> includeHeader =

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,14 @@ final class JsonResumableSessionQueryTask
9292
}
9393
} else if (JsonResumableSessionFailureScenario.isContinue(code)) {
9494
String range1 = response.getHeaders().getRange();
95+
//
9596
if (range1 != null) {
9697
ByteRangeSpec range = ByteRangeSpec.parse(range1);
9798
long endOffset = range.endOffset();
9899
return ResumableOperationResult.incremental(endOffset);
99100
} else {
100-
throw JsonResumableSessionFailureScenario.QUERY_SCENARIO_1.toStorageException(
101-
uploadId, response);
101+
// https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
102+
return ResumableOperationResult.incremental(0);
102103
}
103104
} else {
104105
HttpResponseException cause = new HttpResponseException(response);

google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionQueryTaskTest.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ public void incompleteSession() throws Exception {
141141
}
142142
}
143143

144-
/**
145-
* This is a hard failure from the perspective of GCS as a range header is a required header to be
146-
* included in the response to a query upload request.
147-
*/
148144
@Test
149145
public void incompleteSession_missingRangeHeader() throws Exception {
150146
HttpRequestHandler handler =
@@ -156,9 +152,9 @@ public void incompleteSession_missingRangeHeader() throws Exception {
156152
JsonResumableSessionQueryTask task =
157153
new JsonResumableSessionQueryTask(httpClientContext, uploadUrl);
158154

159-
StorageException se = assertThrows(StorageException.class, task::call);
160-
assertThat(se.getCode()).isEqualTo(503);
161-
assertThat(se).hasMessageThat().contains("Range");
155+
ResumableOperationResult<@Nullable StorageObject> result = task.call();
156+
assertThat(result.getPersistedSize()).isEqualTo(0);
157+
assertThat(result.getObject()).isNull();
162158
}
163159
}
164160

google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ public void xGoogStoredHeadersIncludedIfPresent() throws IOException {
139139
assertThat(storageException).hasMessageThat().contains("|< x-goog-stored-something: blah");
140140
}
141141

142+
@Test
143+
public void xGoogGcsIdempotencyTokenHeadersIncludedIfPresent() throws IOException {
144+
HttpRequest req =
145+
new MockHttpTransport()
146+
.createRequestFactory()
147+
.buildPutRequest(new GenericUrl("http://localhost:80980"), new EmptyContent());
148+
req.getHeaders().setContentLength(0L);
149+
150+
HttpResponse resp = req.execute();
151+
resp.getHeaders().set("X-Goog-Gcs-Idempotency-Token", "5").setContentLength(0L);
152+
153+
StorageException storageException =
154+
JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException(
155+
"uploadId", resp, null, () -> null);
156+
157+
assertThat(storageException.getCode()).isEqualTo(0);
158+
assertThat(storageException).hasMessageThat().contains("|< x-goog-gcs-idempotency-token: 5");
159+
}
160+
142161
private static final class Cause extends RuntimeException {
143162

144163
private Cause() {

0 commit comments

Comments
 (0)