Skip to content

feat(gax): add HTTP/JSON implementation of ResumableUploadClient.startUpload - #14091

Draft
whowes wants to merge 1 commit into
whowes/resumable-upload-clientfrom
whowes/resumable-upload-start
Draft

feat(gax): add HTTP/JSON implementation of ResumableUploadClient.startUpload#14091
whowes wants to merge 1 commit into
whowes/resumable-upload-clientfrom
whowes/resumable-upload-start

Conversation

@whowes

@whowes whowes commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Stack created with GitHub Stacks CLIGive Feedback 💬

@whowes whowes changed the title whowes/resumable upload start feat(gax): add ResumableUploadClient startUpload and HTTP/JSON implementation Aug 17, 2026

@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 a new low-level resumable upload client framework for HTTP/JSON transport, including the ResumableUploadClient interface, ResumableUploadSession metadata, and StartUploadRequest parameters, along with their corresponding unit tests. Feedback on the implementation suggests simplifying the response header parsing logic in HttpJsonResumableUploadClient by removing an unnecessary instanceof HttpHeaders check that is likely always false.

Comment on lines +160 to +166
HttpHeaders headers;
if (responseHeaders.getHeaders() instanceof HttpHeaders) {
headers = (HttpHeaders) responseHeaders.getHeaders();
} else {
headers = new HttpHeaders();
headers.putAll(responseHeaders.getHeaders());
}

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.

medium

The instanceof HttpHeaders check is likely always false, as HttpJsonMetadata.getHeaders() returns a Map<String, List<String>> which is usually not an HttpHeaders instance at runtime. This logic can be simplified by removing the conditional and always creating a new HttpHeaders object. This makes the code cleaner and removes a potentially dead code path.

        HttpHeaders headers = new HttpHeaders();
        headers.putAll(responseHeaders.getHeaders());

@whowes
whowes force-pushed the whowes/resumable-upload-start branch 3 times, most recently from 16d875f to e808397 Compare August 17, 2026 23:44
@whowes
whowes changed the base branch from whowes/string-http-response-parser to whowes/resumable-upload-client August 17, 2026 23:46
@whowes whowes changed the title feat(gax): add ResumableUploadClient startUpload and HTTP/JSON implementation feat(gax): add HTTP/JSON implementation of ResumableUploadClient.startUpload Aug 17, 2026
@whowes
whowes force-pushed the whowes/resumable-upload-start branch from e808397 to dbd35b6 Compare August 17, 2026 23:58
@whowes
whowes force-pushed the whowes/resumable-upload-start branch from dbd35b6 to 1435243 Compare August 18, 2026 00:03
@whowes
whowes force-pushed the whowes/resumable-upload-start branch from 1435243 to 880655e Compare August 18, 2026 00:19
@whowes

whowes commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 the HttpJsonResumableUploadClient class and its associated tests to support resumable uploads over HTTP/JSON. The review feedback suggests optimizing the onHeaders method by replacing the heavy HttpHeaders class with a direct, case-insensitive lookup on the raw headers map to avoid reflection and unnecessary allocations. Consequently, the unused import for HttpHeaders should also be removed.

*/
package com.google.api.gax.httpjson;

import com.google.api.client.http.HttpHeaders;

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.

medium

With the removal of com.google.api.client.http.HttpHeaders from the onHeaders method, this import is no longer needed and can be removed.

Comment on lines +157 to +180
@Override
public void onHeaders(HttpJsonMetadata responseHeaders) {
if (responseHeaders != null && responseHeaders.getHeaders() != null) {
HttpHeaders headers = new HttpHeaders();
headers.putAll(responseHeaders.getHeaders());

String url = headers.getFirstHeaderStringValue(UPLOAD_URL_HEADER);
if (Strings.isNullOrEmpty(url)) {
url = headers.getLocation();
}
if (!Strings.isNullOrEmpty(url)) {
this.uploadUrl = url;
}

String granularityStr = headers.getFirstHeaderStringValue(UPLOAD_GRANULARITY_HEADER);
if (!Strings.isNullOrEmpty(granularityStr)) {
try {
this.chunkGranularity = Long.parseLong(granularityStr);
} catch (NumberFormatException ignored) {
this.chunkGranularity = 1L;
}
}
}
}

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.

medium

Using com.google.api.client.http.HttpHeaders and calling putAll with all response headers is highly inefficient. HttpHeaders is a heavy class that uses reflection to map header keys to class fields. Since we only need to extract a couple of specific headers case-insensitively, we can perform a direct case-insensitive lookup on the raw headers map. This avoids unnecessary object allocation and reflection overhead.

    @Override
    public void onHeaders(HttpJsonMetadata responseHeaders) {
      if (responseHeaders != null && responseHeaders.getHeaders() != null) {
        Map<String, List<String>> headers = responseHeaders.getHeaders();

        String url = getFirstHeader(headers, UPLOAD_URL_HEADER);
        if (Strings.isNullOrEmpty(url)) {
          url = getFirstHeader(headers, "Location");
        }
        if (!Strings.isNullOrEmpty(url)) {
          this.uploadUrl = url;
        }

        String granularityStr = getFirstHeader(headers, UPLOAD_GRANULARITY_HEADER);
        if (!Strings.isNullOrEmpty(granularityStr)) {
          try {
            this.chunkGranularity = Long.parseLong(granularityStr);
          } catch (NumberFormatException ignored) {
            this.chunkGranularity = 1L;
          }
        }
      }
    }

    @Nullable
    private static String getFirstHeader(Map<String, List<String>> headers, String name) {
      for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
        if (entry.getKey().equalsIgnoreCase(name)) {
          List<String> values = entry.getValue();
          return values != null && !values.isEmpty() ? values.get(0) : null;
        }
      }
      return null;
    }
References
  1. When annotating a method with @Nullable, verify if any callers pass the returned value directly to APIs that do not accept nulls (such as Guava's ImmutableMap.Builder). If null checks are missing, they should be added or tracked for follow-up work.

@whowes
whowes force-pushed the whowes/resumable-upload-start branch from 880655e to 2ad83c8 Compare August 18, 2026 00:28
@whowes
whowes force-pushed the whowes/resumable-upload-start branch from 2ad83c8 to 2937dba Compare August 18, 2026 00:35
@whowes whowes added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 18, 2026
@sonarqubecloud

Copy link
Copy Markdown

@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%)

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

kokoro:force-run Add this label to force Kokoro to re-run the tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant