#286 - Send content-id in batch requests
This commit is contained in:
parent
ed40d7b750
commit
8b296b148e
|
@ -31,7 +31,7 @@ require 'google/apis/core/http_command'
|
||||||
require 'google/apis/core/upload'
|
require 'google/apis/core/upload'
|
||||||
require 'google/apis/core/download'
|
require 'google/apis/core/download'
|
||||||
require 'addressable/uri'
|
require 'addressable/uri'
|
||||||
|
require 'securerandom'
|
||||||
module Google
|
module Google
|
||||||
module Apis
|
module Apis
|
||||||
module Core
|
module Core
|
||||||
|
@ -47,6 +47,7 @@ module Google
|
||||||
def initialize(method, url)
|
def initialize(method, url)
|
||||||
super(method, url)
|
super(method, url)
|
||||||
@calls = []
|
@calls = []
|
||||||
|
@base_id = SecureRandom.uuid
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -78,9 +79,12 @@ module Google
|
||||||
parts = split_parts(body, m[1])
|
parts = split_parts(body, m[1])
|
||||||
deserializer = CallDeserializer.new
|
deserializer = CallDeserializer.new
|
||||||
parts.each_index do |index|
|
parts.each_index do |index|
|
||||||
call, callback = @calls[index]
|
response = deserializer.to_http_response(parts[index])
|
||||||
|
outer_header = response.shift
|
||||||
|
call_id = header_to_id(outer_header[:content_id]) || index
|
||||||
|
call, callback = @calls[call_id]
|
||||||
begin
|
begin
|
||||||
result = call.process_response(*deserializer.to_http_response(parts[index])) unless call.nil?
|
result = call.process_response(*response) unless call.nil?
|
||||||
success(result, &callback)
|
success(result, &callback)
|
||||||
rescue => e
|
rescue => e
|
||||||
error(e, &callback)
|
error(e, &callback)
|
||||||
|
@ -103,9 +107,11 @@ module Google
|
||||||
|
|
||||||
serializer = CallSerializer.new
|
serializer = CallSerializer.new
|
||||||
multipart = Multipart.new(boundary: BATCH_BOUNDARY, content_type: MULTIPART_MIXED)
|
multipart = Multipart.new(boundary: BATCH_BOUNDARY, content_type: MULTIPART_MIXED)
|
||||||
@calls.each do |(call, _)|
|
@calls.each_index do |index|
|
||||||
|
call, _ = @calls[index]
|
||||||
|
content_id = id_to_header(index)
|
||||||
io = serializer.to_upload_io(call)
|
io = serializer.to_upload_io(call)
|
||||||
multipart.add_upload(io)
|
multipart.add_upload(io, content_id: content_id)
|
||||||
end
|
end
|
||||||
self.body = multipart.assemble
|
self.body = multipart.assemble
|
||||||
|
|
||||||
|
@ -120,6 +126,17 @@ module Google
|
||||||
end
|
end
|
||||||
fail Google::Apis::ClientError, 'Invalid command object' unless command.is_a?(HttpCommand)
|
fail Google::Apis::ClientError, 'Invalid command object' unless command.is_a?(HttpCommand)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def id_to_header(call_id)
|
||||||
|
return sprintf('<%s+%i>', @base_id, call_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def header_to_id(content_id)
|
||||||
|
match = /<response-.*\+(\d+)>/.match(content_id)
|
||||||
|
return match[1].to_i if match
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wrapper request for batching multiple uploads in a single server request
|
# Wrapper request for batching multiple uploads in a single server request
|
||||||
|
@ -180,19 +197,19 @@ module Google
|
||||||
# Deconstructs a raw HTTP response part
|
# Deconstructs a raw HTTP response part
|
||||||
# @private
|
# @private
|
||||||
class CallDeserializer
|
class CallDeserializer
|
||||||
# Convert a single batched response into a BatchedCallResponse object.
|
# Parse a batched response.
|
||||||
#
|
#
|
||||||
# @param [String] call_response
|
# @param [String] call_response
|
||||||
# the response to parse.
|
# the response to parse.
|
||||||
# @return [Array<(Fixnum, Hurley::Header, String)>]
|
# @return [Array<(Fixnum, Hurley::Header, String)>]
|
||||||
# Status, header, and response body.
|
# Status, header, and response body.
|
||||||
def to_http_response(call_response)
|
def to_http_response(call_response)
|
||||||
_, outer_body = split_header_and_body(call_response)
|
outer_header, outer_body = split_header_and_body(call_response)
|
||||||
status_line, payload = outer_body.split(/\n/, 2)
|
status_line, payload = outer_body.split(/\n/, 2)
|
||||||
_, status = status_line.split(' ', 3)
|
_, status = status_line.split(' ', 3)
|
||||||
|
|
||||||
header, body = split_header_and_body(payload)
|
header, body = split_header_and_body(payload)
|
||||||
[status.to_i, header, body]
|
[outer_header, status.to_i, header, body]
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -31,7 +31,7 @@ module Google
|
||||||
# Multipart boundary
|
# Multipart boundary
|
||||||
# @param [String] value
|
# @param [String] value
|
||||||
# JSON content
|
# JSON content
|
||||||
def initialize(boundary, value)
|
def initialize(boundary, value, header = {})
|
||||||
@part = build_part(boundary, value)
|
@part = build_part(boundary, value)
|
||||||
@length = @part.bytesize
|
@length = @part.bytesize
|
||||||
@io = StringIO.new(@part)
|
@io = StringIO.new(@part)
|
||||||
|
@ -95,19 +95,25 @@ module Google
|
||||||
# @param [Hash] header
|
# @param [Hash] header
|
||||||
# Headers for the part
|
# Headers for the part
|
||||||
def build_head(boundary, type, content_len, header)
|
def build_head(boundary, type, content_len, header)
|
||||||
|
content_id = ''
|
||||||
|
if header[:content_id]
|
||||||
|
content_id = sprintf(CID_FORMAT, header[:content_id])
|
||||||
|
end
|
||||||
sprintf(HEAD_FORMAT,
|
sprintf(HEAD_FORMAT,
|
||||||
boundary,
|
boundary,
|
||||||
content_len.to_i,
|
content_len.to_i,
|
||||||
|
content_id,
|
||||||
header[:content_type] || type,
|
header[:content_type] || type,
|
||||||
header[:content_transfer_encoding] || DEFAULT_TR_ENCODING)
|
header[:content_transfer_encoding] || DEFAULT_TR_ENCODING)
|
||||||
end
|
end
|
||||||
|
|
||||||
DEFAULT_TR_ENCODING = 'binary'.freeze
|
DEFAULT_TR_ENCODING = 'binary'.freeze
|
||||||
FOOT = "\r\n".freeze
|
FOOT = "\r\n".freeze
|
||||||
|
CID_FORMAT = "Content-ID: %s\r\n"
|
||||||
HEAD_FORMAT = <<-END
|
HEAD_FORMAT = <<-END
|
||||||
--%s\r
|
--%s\r
|
||||||
Content-Length: %d\r
|
Content-Length: %d\r
|
||||||
Content-Type: %s\r
|
%sContent-Type: %s\r
|
||||||
Content-Transfer-Encoding: %s\r
|
Content-Transfer-Encoding: %s\r
|
||||||
\r
|
\r
|
||||||
END
|
END
|
||||||
|
@ -137,9 +143,12 @@ Content-Transfer-Encoding: %s\r
|
||||||
#
|
#
|
||||||
# @param [String] body
|
# @param [String] body
|
||||||
# JSON text
|
# JSON text
|
||||||
|
# @param [String] content_id
|
||||||
|
# Optional unique ID of this part
|
||||||
# @return [self]
|
# @return [self]
|
||||||
def add_json(body)
|
def add_json(body, content_id: nil)
|
||||||
@parts << Google::Apis::Core::JsonPart.new(@boundary, body)
|
header = { :content_id => content_id }
|
||||||
|
@parts << Google::Apis::Core::JsonPart.new(@boundary, body, header)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -147,9 +156,14 @@ Content-Transfer-Encoding: %s\r
|
||||||
#
|
#
|
||||||
# @param [Google::Apis::Core::UploadIO] upload_io
|
# @param [Google::Apis::Core::UploadIO] upload_io
|
||||||
# IO stream
|
# IO stream
|
||||||
|
# @param [String] content_id
|
||||||
|
# Optional unique ID of this part
|
||||||
# @return [self]
|
# @return [self]
|
||||||
def add_upload(upload_io)
|
def add_upload(upload_io, content_id: nil)
|
||||||
@parts << Google::Apis::Core::FilePart.new(@boundary, upload_io)
|
header = { :content_id => content_id }
|
||||||
|
@parts << Google::Apis::Core::FilePart.new(@boundary,
|
||||||
|
upload_io,
|
||||||
|
header)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,12 @@ RSpec.describe Google::Apis::Core::BatchCommand do
|
||||||
end
|
end
|
||||||
|
|
||||||
before(:example) do
|
before(:example) do
|
||||||
|
allow(SecureRandom).to receive(:uuid).and_return('ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f')
|
||||||
|
|
||||||
response = <<EOF
|
response = <<EOF
|
||||||
--batch123
|
--batch123
|
||||||
Content-Type: application/http
|
Content-Type: application/http
|
||||||
|
Content-ID: <response-ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+0>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
@ -52,18 +55,20 @@ Content-Type: text/plain; charset=UTF-8
|
||||||
Hello
|
Hello
|
||||||
--batch123
|
--batch123
|
||||||
Content-Type: application/http
|
Content-Type: application/http
|
||||||
|
Content-ID: <response-ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+2>
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: text/plain; charset=UTF-8
|
|
||||||
|
|
||||||
world
|
|
||||||
--batch123
|
|
||||||
Content-Type: application/http
|
|
||||||
|
|
||||||
HTTP/1.1 500 Server Error
|
HTTP/1.1 500 Server Error
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
Error!
|
Error!
|
||||||
|
--batch123
|
||||||
|
Content-Type: application/http
|
||||||
|
Content-ID: <response-ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+1>
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
world
|
||||||
--batch123--
|
--batch123--
|
||||||
EOF
|
EOF
|
||||||
stub_request(:post, 'https://www.googleapis.com/batch')
|
stub_request(:post, 'https://www.googleapis.com/batch')
|
||||||
|
@ -80,6 +85,7 @@ EOF
|
||||||
expected_body = <<EOF.gsub(/\n/, "\r\n")
|
expected_body = <<EOF.gsub(/\n/, "\r\n")
|
||||||
--RubyApiBatchRequest
|
--RubyApiBatchRequest
|
||||||
Content-Length: 58
|
Content-Length: 58
|
||||||
|
Content-ID: <ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+0>
|
||||||
Content-Type: application/http
|
Content-Type: application/http
|
||||||
Content-Transfer-Encoding: binary
|
Content-Transfer-Encoding: binary
|
||||||
|
|
||||||
|
@ -89,6 +95,7 @@ Host: www.googleapis.com
|
||||||
|
|
||||||
--RubyApiBatchRequest
|
--RubyApiBatchRequest
|
||||||
Content-Length: 96
|
Content-Length: 96
|
||||||
|
Content-ID: <ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+1>
|
||||||
Content-Type: application/http
|
Content-Type: application/http
|
||||||
Content-Transfer-Encoding: binary
|
Content-Transfer-Encoding: binary
|
||||||
|
|
||||||
|
@ -99,6 +106,7 @@ Host: www.googleapis.com
|
||||||
Hello world
|
Hello world
|
||||||
--RubyApiBatchRequest
|
--RubyApiBatchRequest
|
||||||
Content-Length: 93
|
Content-Length: 93
|
||||||
|
Content-ID: <ffe23d1b-e8f7-47f5-8c01-2a30cf8ecb8f+2>
|
||||||
Content-Type: application/http
|
Content-Type: application/http
|
||||||
Content-Transfer-Encoding: binary
|
Content-Transfer-Encoding: binary
|
||||||
|
|
||||||
|
@ -115,11 +123,17 @@ EOF
|
||||||
|
|
||||||
it 'should send decode responses' do
|
it 'should send decode responses' do
|
||||||
expect do |b|
|
expect do |b|
|
||||||
command.add(get_command, &b)
|
command.add(get_command) do |res, err|
|
||||||
command.add(post_with_string_command, &b)
|
b.to_proc.call(1, res, err)
|
||||||
command.add(post_with_io_command, &b)
|
end
|
||||||
|
command.add(post_with_string_command) do |res, err|
|
||||||
|
b.to_proc.call(2, res, err)
|
||||||
|
end
|
||||||
|
command.add(post_with_io_command) do |res, err|
|
||||||
|
b.to_proc.call(3, res, err)
|
||||||
|
end
|
||||||
command.execute(client)
|
command.execute(client)
|
||||||
end.to yield_successive_args(['Hello', nil], ['world', nil], [nil, an_instance_of(Google::Apis::ServerError)])
|
end.to yield_successive_args([1, 'Hello', nil], [3, nil, an_instance_of(Google::Apis::ServerError)], [2, 'world', nil],)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should raise error if batch is empty' do
|
it 'should raise error if batch is empty' do
|
||||||
|
|
Loading…
Reference in New Issue