Skip to content

feat(gax): implement uploadChunkCallable for resumable uploads - #14140

Merged
whowes merged 1 commit into
mainfrom
whowes/resumable-upload-chunk
Sep 2, 2026
Merged

feat(gax): implement uploadChunkCallable for resumable uploads#14140
whowes merged 1 commit into
mainfrom
whowes/resumable-upload-chunk

Conversation

@whowes

@whowes whowes commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

This handles sending binary chunks and finalize commands over HTTP/JSON, extracting offset and status from response headers/codes.

gemini-code-assist[bot]

This comment was marked as outdated.

@whowes whowes changed the title whowes/resumable upload chunk feat(gax): implement uploadChunk in HttpJsonResumableUploadClient Aug 19, 2026
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from faf99d0 to 010c0aa Compare August 19, 2026 23:36
@whowes whowes changed the title feat(gax): implement uploadChunk in HttpJsonResumableUploadClient feat(gax): implement uploadChunk in HttpJsonResumableUploadClient Aug 19, 2026
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 010c0aa to baee7f0 Compare August 20, 2026 00:06
@whowes

whowes commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from baee7f0 to 8a6d2fc Compare August 20, 2026 00:10

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for transmitting individual chunks in a resumable upload session by adding ChunkUploadRequest and ChunkUploadResponse classes, implementing uploadChunkCallable() in HttpJsonResumableUploadClient, and adding corresponding unit tests. Feedback was provided to change the HTTP method in UPLOAD_CHUNK_DESCRIPTOR from POST to PUT to comply with the Google Scotty resumable upload protocol.

@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 8a6d2fc to 1c123a9 Compare August 20, 2026 01:38
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 1c123a9 to d1a4963 Compare August 20, 2026 20:56
@whowes whowes changed the title feat(gax): implement uploadChunk in HttpJsonResumableUploadClient feat(gax): implement uploadChunkCallable for resumable uploads Aug 21, 2026
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from d1a4963 to 0dcb287 Compare August 21, 2026 00:37
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch 3 times, most recently from 839a11f to e6fd648 Compare August 25, 2026 01:03
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from e6fd648 to c851386 Compare August 25, 2026 16:34
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from c851386 to 3e9ab99 Compare August 25, 2026 21:44
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 3e9ab99 to 4937751 Compare August 25, 2026 22:00
@whowes

whowes commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from d3abc11 to f4acebc Compare August 27, 2026 01:50
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from f4acebc to 5268b3c Compare August 27, 2026 15:27
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 5268b3c to 7b0fff7 Compare August 27, 2026 20:15
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch 2 times, most recently from 8e33b86 to a6c4e6d Compare August 27, 2026 23:52
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from a6c4e6d to f91782f Compare August 28, 2026 01:34
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch 2 times, most recently from bbcdbb0 to 780a677 Compare August 28, 2026 16:49
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 780a677 to 65db78a Compare August 28, 2026 17:02
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 65db78a to 356b549 Compare August 28, 2026 17:23
@whowes
whowes force-pushed the whowes/resumable-upload-chunk branch from 356b549 to 2beec8b Compare August 28, 2026 19:11
public abstract String getUploadUrl();

/** The binary chunk payload to upload. */
public abstract ByteString getPayload();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a Java native type like byte[]?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern with the byte array is that it breaks the typical AutoValue immutability contract (unless using defensive copying which would be pretty expensive for multi-MB chunks). For Java native, ByteBuffer is the other option that comes to mind but it's stateful so not really suitable for use here.

Using ByteString seems to be within precedent when an immutable array/List of bytes is needed see e.g. ByteArray that uses it as backing storage even though the class itself isn't really protobuf-specific, wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern with the byte array is that it breaks the typical AutoValue immutability contract

I think this is a valid concern but it is acceptable compare to other concerns. My two concerns are:

  1. Unnecessary converting from InputStream to ByteString and then to byte[]. ResumableUploadCallable accepts an InputStream, so we would have to create a ByteString from it first in the upstream callable and get byte[] from it in low level callable.
  2. Protobuf types such as ByteString are already exposed on the surface but we don't want to make it worse. Protobuf could still make breaking changes so using Java native types are preferred in general.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM, switched to byte[]


@Override
public String getPath(ChunkUploadRequest request) {
return request.getUploadUrl();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we designed request.getUploadUrl to be a full url in previous PRs. But I'm leaning towards splitting the full url returned from server to endpoint and path to follow existing patterns now.

For example, split https://foo.com/upload?upload_id=xyz to https://foo.com as endpoint, upload as path, and upload_id=xyz as query parameters.
This is because we have some places like the getPathTemplate method below assuming the structure of the path and using a placeholder like PathTemplate.create("{+path}") could be misleading.

On the other hand, I know it might not be easy to split the full urls in a clean way either. Let me know what you think.

@whowes whowes Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know that we'd ever be able to use anything more specific than the {+path} placeholder here though since that may vary across the various services that use resumable uploads? e.g. we can't assume that we can parse out any upload ID (that limitation in particular is called out in one of our internal requirements docs.) I don't know that we even can assume that "upload" will be in the path.

In practice for the upload protocol code using the URL as an opaque string is fine since we just make requests directly to the URL without needing to make additions or subtractions to it (so there wouldn't really be much benefit to the protocol code specifically where this would be helpful), are there assumptions made elsewhere in GAX about the URL structure where this is problematic?

I'm not completely opposed to parsing here, just trying to understand the benefits v. complexity it would add (and probably preferring to defer that refactor to a future PR).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM. Let's use PathTemplate.create("**") as a dummy placeholder for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, switched to "**" here and other relevant places.

return future;
}

static <ResponseT> UnaryCallable<ChunkUploadRequest, ChunkUploadResponse<ResponseT>> create(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the creation of the callable is not done in the callable itself. For example, createOperationCallable in HttpJsonCallableFactory.
We probably need to do the same for the high level ResumableUploadCallable. For the low level callables, I think it is OK to do this for now in this PR, but we need to give it more thought later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, I agree that ResumableUploadCallable creation definitely belongs there, and we can consider consolidating the lower-level Callable factory methods there too in the future.

public abstract String getUploadUrl();

/** The binary chunk payload to upload. */
public abstract ByteString getPayload();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern with the byte array is that it breaks the typical AutoValue immutability contract

I think this is a valid concern but it is acceptable compare to other concerns. My two concerns are:

  1. Unnecessary converting from InputStream to ByteString and then to byte[]. ResumableUploadCallable accepts an InputStream, so we would have to create a ByteString from it first in the upstream callable and get byte[] from it in low level callable.
  2. Protobuf types such as ByteString are already exposed on the surface but we don't want to make it worse. Protobuf could still make breaking changes so using Java native types are preferred in general.

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants