Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle empty response body case #112

Merged
merged 4 commits into from
Sep 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 11 additions & 10 deletions lib/td/client/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ def do_get(url, params=nil, &block)
if block
current_total_chunk_size = 0
response = client.get(target, params, header) {|res, chunk|
break if res.status != 200
current_total_chunk_size += chunk.bytesize
block.call(res, chunk, current_total_chunk_size)
}
Expand All @@ -300,17 +301,17 @@ def do_get(url, params=nil, &block)
else
response = client.get(target, params, header)

validate_content_length!(response, response.body.bytesize) if @ssl
end
status = response.code
# retry if the HTTP error code is 500 or higher and we did not run out of retrying attempts
if status >= 500 && cumul_retry_delay < @max_cumul_retry_delay
$stderr.puts "Error #{status}: #{get_error(response)}. Retrying after #{retry_delay} seconds..."
sleep retry_delay
cumul_retry_delay += retry_delay
retry_delay *= 2
redo # restart from beginning of do-while loop
Copy link
Contributor

Choose a reason for hiding this comment

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

Original code had redo out of if-else. Now it only exists in else.
Thus don't retry if block given and response code is 5xx. Is this behavior change intentional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Original code also had redo inside if !block_given?, isn't it?

Why not retry if block given is because if block given the block may be not idempotent.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I meant if-else for if block ... else ...

if block
current_total_chunk_size = 0
response = client.get(target, params, header) {|res, chunk|
current_total_chunk_size += chunk.bytesize
block.call(res, chunk, current_total_chunk_size)
}
# XXX ext/openssl raises EOFError in case where underlying connection causes an error,
# and msgpack-ruby that used in block handles it as an end of stream == no exception.
# Therefor, check content size.
validate_content_length!(response, current_total_chunk_size) if @ssl
else
response = client.get(target, params, header)
validate_content_length!(response, response.body.bytesize) if @ssl
end
status = response.code
# retry if the HTTP error code is 500 or higher and we did not run out of retrying attempts
if !block_given? && status >= 500 && cumul_retry_delay < @max_cumul_retry_delay
$stderr.puts "Error #{status}: #{get_error(response)}. Retrying after #{retry_delay} seconds..."
sleep retry_delay
cumul_retry_delay += retry_delay
retry_delay *= 2
redo # restart from beginning of do-while loop
end

if block
  ...
else
  ...
end

if !block_given? && ..
  ...
  redo
end

changed to

if block
  ...
else
  ...
  if !block_given? && ...
    ...
    redo
  end
end

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah nvm, sorry I got wrong about it. Behavior didn't changed

end

status = response.code
# retry if the HTTP error code is 500 or higher and we did not run out of retrying attempts
if !block_given? && status >= 500 && cumul_retry_delay < @max_cumul_retry_delay
$stderr.puts "Error #{status}: #{get_error(response)}. Retrying after #{retry_delay} seconds..."
sleep retry_delay
cumul_retry_delay += retry_delay
retry_delay *= 2
redo # restart from beginning of do-while loop
validate_content_length!(response, response.body.to_s.bytesize) if @ssl
end
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError,
SystemCallError, OpenSSL::SSL::SSLError, SocketError, HTTPClient::TimeoutError,
Expand Down
54 changes: 29 additions & 25 deletions lib/td/client/api/job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,22 @@ def validate_content_length_with_range(response, current_total_chunk_size)
end
end

def validate_response_status(res, current_total_chunk_size=0)
case res.status
when 200
if current_total_chunk_size != 0
# try to resume but the server returns 200
raise_error("Get job result failed", res)
end
when 206 # resuming
else
if res.status/100 == 5
raise HTTPServerException
end
raise_error("Get job result failed", res)
end
end

class HTTPServerException < StandardError
end

Expand All @@ -297,43 +313,31 @@ def job_result_download(job_id, format='msgpack', autodecode=true)
current_total_chunk_size = 0
infl = nil
begin # LOOP of Network/Server errors
response = nil
client.get(url, params, header) do |res, chunk|
unless response
case res.status
when 200
if current_total_chunk_size != 0
# try to resume but the server returns 200
raise_error("Get job result failed", res)
end
when 206 # resuming
else
if res.status/100 == 5
raise HTTPServerException
end
raise_error("Get job result failed", res)
end
if infl.nil? && autodecode
case res.header['Content-Encoding'][0].to_s.downcase
when 'gzip'
infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
when 'deflate'
infl = Zlib::Inflate.new
end
response = client.get(url, params, header) do |res, chunk|
validate_response_status(res, current_total_chunk_size)
if infl.nil? && autodecode
case res.header['Content-Encoding'][0].to_s.downcase
when 'gzip'
infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
when 'deflate'
infl = Zlib::Inflate.new
end
end
response = res
current_total_chunk_size += chunk.bytesize
chunk = infl.inflate(chunk) if infl
yield chunk, current_total_chunk_size
end

# for the case response body is empty
# Note that webmock returns response.body as "" instead of nil
validate_response_status(response, 0) if response.body.to_s.empty?

# completed?
validate_content_length_with_range(response, current_total_chunk_size)
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError,
SystemCallError, OpenSSL::SSL::SSLError, SocketError, HTTPClient::TimeoutError,
HTTPServerException => e
if response # at least a chunk is downloaded
if current_total_chunk_size > 0
if etag = response.header['ETag'][0]
header['If-Range'] = etag
header['Range'] = "bytes=#{current_total_chunk_size}-"
Expand Down
9 changes: 9 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@

include TreasureData

RSpec.configure do |config|
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
end

shared_context 'common helper' do
let :account_id do
1
Expand Down
21 changes: 21 additions & 0 deletions spec/td/client/job_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,27 @@
expect(api.job_result(12345)).to eq ['hello', 'world']
end

it '200 (blank) -> 200 works fine' do
i = 0
stub_api_request(:get, '/v3/job/result/12345').
with(:query => {'format' => 'msgpack'}).
to_return do
i += 1
puts "count : #{i}"
{
:headers => {
'Content-Length' => packed.bytesize,
'Etag' => '"abcdefghijklmn"',
},
:body => (i == 1 ? nil : packed)
}
end
expect(api).to receive(:sleep).once
expect($stderr).to receive(:print)
expect($stderr).to receive(:puts)
expect(api.job_result(12345)).to eq ['hello', 'world']
end

end

describe 'job_result_format' do
Expand Down