Skip to content

Commit 0f5a074

Browse files
fear(core): setting idempotency header (googleapis#22517)
1 parent cf8b80e commit 0f5a074

File tree

3 files changed

+31
-4
lines changed

3 files changed

+31
-4
lines changed

google-apis-core/lib/google/apis/core/api_command.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
require 'google/apis/errors'
1919
require 'json'
2020
require 'retriable'
21-
require "securerandom"
2221

2322
module Google
2423
module Apis
@@ -70,6 +69,7 @@ def initialize(method, url, body: nil, client_version: nil)
7069
def prepare!
7170
set_api_client_header
7271
set_user_project_header
72+
set_idempotency_token_header
7373
if options&.api_format_version
7474
header['X-Goog-Api-Format-Version'] = options.api_format_version.to_s
7575
end
@@ -143,6 +143,7 @@ def allow_form_encoding?
143143
end
144144

145145
private
146+
INVOCATION_ID = SecureRandom.uuid
146147

147148
def set_api_client_header
148149
old_xgac = header
@@ -174,8 +175,12 @@ def set_user_project_header
174175
header['X-Goog-User-Project'] = quota_project_id if quota_project_id
175176
end
176177

178+
def set_idempotency_token_header
179+
header['X-Goog-Gcs-Idempotency-Token'] = INVOCATION_ID
180+
end
181+
177182
def invocation_id_header
178-
"gccl-invocation-id/#{SecureRandom.uuid}"
183+
"gccl-invocation-id/#{INVOCATION_ID}"
179184
end
180185

181186
# Attempt to parse a JSON error message

google-apis-core/spec/google/apis/core/api_command_spec.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@
3434

3535
let(:client_version) { "1.2.3" }
3636
let(:x_goog_api_client_value) { "gl-ruby/#{RUBY_VERSION} gdcl/#{client_version}" }
37-
3837
context('with preparation') do
3938
let(:command) do
4039
Google::Apis::Core::ApiCommand.new(:get, 'https://www.googleapis.com/zoo/animals', client_version: client_version)
4140
end
41+
let(:invocation_id) { 'test123' }
42+
43+
before(:example) do
44+
Google::Apis::Core::ApiCommand.const_set(:INVOCATION_ID, invocation_id)
45+
end
4246

4347
it 'should set X-Goog-Api-Client header if none is set' do
4448
command.prepare!
@@ -89,6 +93,14 @@
8993
command.options.add_invocation_id_header = true
9094
command.prepare!
9195
expect(command.header["X-Goog-Api-Client"]).to include("gccl-invocation-id")
96+
expect(command.header["X-Goog-Api-Client"]).to include(invocation_id)
97+
98+
end
99+
100+
it "should set the X-Goog-Gcs-Idempotency-Token header" do
101+
command.prepare!
102+
expect(command.header['X-Goog-Gcs-Idempotency-Token']).not_to be_nil
103+
expect(command.header['X-Goog-Gcs-Idempotency-Token']).to eql invocation_id
92104
end
93105
end
94106

@@ -250,11 +262,18 @@
250262
command.options.add_invocation_id_header = true
251263
result = command.execute(client)
252264
invocation_id_header = command.header["X-Goog-Api-Client"]
253-
254265
expect(invocation_id_header).to include("gccl-invocation-id")
255266
expect(a_request(:get, 'https://www.googleapis.com/zoo/animals')
256267
.with { |req| req.headers['X-Goog-Api-Client'] == invocation_id_header }).to have_been_made.times(2)
257268
end
269+
270+
it 'should keep same idempotency_token across retries' do
271+
result = command.execute(client)
272+
idempotency_token_header = command.header['X-Goog-Gcs-Idempotency-Token']
273+
expect(a_request(:get, 'https://www.googleapis.com/zoo/animals')
274+
.with { |req| req.headers['X-Goog-Gcs-Idempotency-Token'] == idempotency_token_header })
275+
.to have_been_made.times(2)
276+
end
258277
end
259278

260279
context('with a project not linked response') do

google-apis-core/spec/google/apis/core/service_spec.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
let(:service_ud) { Google::Apis::Core::BaseService.new('https://www.$UNIVERSE_DOMAIN$/', '', client_version: client_version) }
2626
let(:service_with_base_path) { Google::Apis::Core::BaseService.new('https://www.googleapis.com/', 'my_service/v1/', client_version: client_version) }
2727
let(:x_goog_api_client_value) { "gl-ruby/#{RUBY_VERSION} gdcl/#{client_version}" }
28+
let(:invocation_id) { 'test123' }
2829

2930
before do
3031
Google::Apis::ClientOptions.default.application_name = 'test'
@@ -374,6 +375,7 @@
374375

375376
context 'with batch uploads' do
376377
before(:example) do
378+
Google::Apis::Core::ApiCommand.const_set(:INVOCATION_ID, invocation_id)
377379
allow(SecureRandom).to receive(:uuid).and_return('b1981e17-f622-49af-b2eb-203308b1b17d')
378380
allow(Digest::SHA1).to receive(:hexdigest).and_return('outer', 'inner')
379381
response = <<EOF.gsub(/\n/, "\r\n")
@@ -430,6 +432,7 @@
430432
431433
POST /upload/zoo/animals\\? HTTP/1\\.1
432434
X-Goog-Api-Client: #{Regexp.escape(x_goog_api_client_value)}
435+
X-Goog-Gcs-Idempotency-Token: #{invocation_id}
433436
Content-Type: multipart/related; boundary=inner
434437
X-Goog-Upload-Protocol: multipart
435438
Authorization: Bearer a token

0 commit comments

Comments
 (0)