From a8e0a34c47c79891ec76250a3b1fdfec4aa41ced Mon Sep 17 00:00:00 2001 From: buzz Date: Mon, 9 Mar 2026 18:25:24 +0900 Subject: [PATCH] fix(core): fix Content-Length mismatch in resumable upload initiation The `initiate_resumable_upload` method was setting the `Content-Length` header to `upload_io.size` (the total file size), but the request body only contains small JSON metadata. This worked with the previous HTTPClient backend because HTTPClient auto-recalculates `Content-Length` from the actual body. However, after the Faraday migration (#23524), HTTP adapters like Excon respect user-set headers as-is, causing a deadlock: the server waits for more data matching the large `Content-Length`, while the client waits for a response. Fix: Remove the incorrect `Content-Length` header (let the HTTP adapter compute it from the body) and use `X-Upload-Content-Length` to communicate the total file size per the resumable upload protocol. ref: https://cloud.google.com/storage/docs/performing-resumable-uploads --- google-apis-core/lib/google/apis/core/storage_upload.rb | 3 ++- .../spec/google/apis/core/storage_upload_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/google-apis-core/lib/google/apis/core/storage_upload.rb b/google-apis-core/lib/google/apis/core/storage_upload.rb index da31878d3a3..cd7e7823207 100644 --- a/google-apis-core/lib/google/apis/core/storage_upload.rb +++ b/google-apis-core/lib/google/apis/core/storage_upload.rb @@ -28,6 +28,7 @@ class StorageUploadCommand < ApiCommand CONTENT_LENGTH_HEADER = "Content-Length" CONTENT_TYPE_HEADER = "Content-Type" UPLOAD_CONTENT_TYPE_HEADER = "X-Upload-Content-Type" + UPLOAD_CONTENT_LENGTH_HEADER = "X-Upload-Content-Length" LOCATION_HEADER = "Location" CONTENT_RANGE_HEADER = "Content-Range" RESUMABLE = "resumable" @@ -133,9 +134,9 @@ def initiate_resumable_upload(client) request_query = query.dup request_query['uploadType'] = RESUMABLE - request_header[CONTENT_LENGTH_HEADER] = upload_io.size.to_s request_header[CONTENT_TYPE_HEADER] = JSON_CONTENT_TYPE request_header[UPLOAD_CONTENT_TYPE_HEADER] = upload_content_type unless upload_content_type.nil? + request_header[UPLOAD_CONTENT_LENGTH_HEADER] = upload_io.size.to_s response = client.post(url.to_s, body, request_header) do |request| request.params.replace(request_query) diff --git a/google-apis-core/spec/google/apis/core/storage_upload_spec.rb b/google-apis-core/spec/google/apis/core/storage_upload_spec.rb index 7435f9cafe8..43bda9fcf68 100644 --- a/google-apis-core/spec/google/apis/core/storage_upload_spec.rb +++ b/google-apis-core/spec/google/apis/core/storage_upload_spec.rb @@ -51,6 +51,12 @@ expect(a_request(:put, 'https://www.googleapis.com/zoo/animals')).to have_been_made end + it 'should send X-Upload-Content-Length in initiate request' do + command.execute(client) + expect(a_request(:post, 'https://www.googleapis.com/zoo/animals?uploadType=resumable') + .with { |req| req.headers.key?('X-Upload-Content-Length') }).to have_been_made + end + it 'should generate a proper opencensus span' do OpenCensus::Trace.start_request_trace do |span_context| command.execute(client)