Commit 8fe298be authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'mk/http-default-timeout' into 'master'

Set stricter default timeouts for HTTP requests

See merge request gitlab-org/gitlab!38039
parents 77d48b0d 3bc4e3bc
...@@ -9,32 +9,51 @@ module Gitlab ...@@ -9,32 +9,51 @@ module Gitlab
BlockedUrlError = Class.new(StandardError) BlockedUrlError = Class.new(StandardError)
RedirectionTooDeep = Class.new(StandardError) RedirectionTooDeep = Class.new(StandardError)
HTTP_ERRORS = [ HTTP_TIMEOUT_ERRORS = [
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
].freeze
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep
Gitlab::HTTP::RedirectionTooDeep
].freeze ].freeze
DEFAULT_TIMEOUT_OPTIONS = {
open_timeout: 10,
read_timeout: 20,
write_timeout: 30
}.freeze
include HTTParty # rubocop:disable Gitlab/HTTParty include HTTParty # rubocop:disable Gitlab/HTTParty
class << self
alias_method :httparty_perform_request, :perform_request
end
connection_adapter HTTPConnectionAdapter connection_adapter HTTPConnectionAdapter
def self.perform_request(http_method, path, options, &block) def self.perform_request(http_method, path, options, &block)
super log_info = options.delete(:extra_log_info)
options_with_timeouts =
if !options.has_key?(:timeout) && Feature.enabled?(:http_default_timeouts)
options.with_defaults(DEFAULT_TIMEOUT_OPTIONS)
else
options
end
httparty_perform_request(http_method, path, options_with_timeouts, &block)
rescue HTTParty::RedirectionTooDeep rescue HTTParty::RedirectionTooDeep
raise RedirectionTooDeep raise RedirectionTooDeep
end
def self.try_get(path, options = {}, &block)
log_info = options.delete(:extra_log_info)
self.get(path, options, &block)
rescue *HTTP_ERRORS => e rescue *HTTP_ERRORS => e
extra_info = log_info || {} extra_info = log_info || {}
extra_info = log_info.call(e, path, options) if log_info.respond_to?(:call) extra_info = log_info.call(e, path, options) if log_info.respond_to?(:call)
Gitlab::ErrorTracking.log_exception(e, extra_info) Gitlab::ErrorTracking.log_exception(e, extra_info)
raise e
end
def self.try_get(path, options = {}, &block)
self.get(path, options, &block)
rescue *HTTP_ERRORS
nil nil
end end
end end
......
...@@ -5,6 +5,8 @@ require 'spec_helper' ...@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::HTTP do RSpec.describe Gitlab::HTTP do
include StubRequests include StubRequests
let(:default_options) { described_class::DEFAULT_TIMEOUT_OPTIONS }
context 'when allow_local_requests' do context 'when allow_local_requests' do
it 'sends the request to the correct URI' do it 'sends the request to the correct URI' do
stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200) stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
...@@ -101,6 +103,73 @@ RSpec.describe Gitlab::HTTP do ...@@ -101,6 +103,73 @@ RSpec.describe Gitlab::HTTP do
end end
end end
describe 'setting default timeouts' do
before do
stub_full_request('http://example.org', method: :any)
end
context 'when no timeouts are set' do
it 'sets default open and read and write timeouts' do
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Get, 'http://example.org', default_options
).and_call_original
described_class.get('http://example.org')
end
end
context 'when :timeout is set' do
it 'does not set any default timeouts' do
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Get, 'http://example.org', timeout: 1
).and_call_original
described_class.get('http://example.org', timeout: 1)
end
end
context 'when :open_timeout is set' do
it 'only sets default read and write timeout' do
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Get, 'http://example.org', default_options.merge(open_timeout: 1)
).and_call_original
described_class.get('http://example.org', open_timeout: 1)
end
end
context 'when :read_timeout is set' do
it 'only sets default open and write timeout' do
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Get, 'http://example.org', default_options.merge(read_timeout: 1)
).and_call_original
described_class.get('http://example.org', read_timeout: 1)
end
end
context 'when :write_timeout is set' do
it 'only sets default open and read timeout' do
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Put, 'http://example.org', default_options.merge(write_timeout: 1)
).and_call_original
described_class.put('http://example.org', write_timeout: 1)
end
end
context 'when default timeouts feature is disabled' do
it 'does not apply any defaults' do
stub_feature_flags(http_default_timeouts: false)
expect(described_class).to receive(:httparty_perform_request).with(
Net::HTTP::Get, 'http://example.org', open_timeout: 1
).and_call_original
described_class.get('http://example.org', open_timeout: 1)
end
end
end
describe '.try_get' do describe '.try_get' do
let(:path) { 'http://example.org' } let(:path) { 'http://example.org' }
...@@ -111,10 +180,10 @@ RSpec.describe Gitlab::HTTP do ...@@ -111,10 +180,10 @@ RSpec.describe Gitlab::HTTP do
end end
let(:request_options) do let(:request_options) do
{ default_options.merge({
verify: false, verify: false,
basic_auth: { username: 'user', password: 'pass' } basic_auth: { username: 'user', password: 'pass' }
} })
end end
described_class::HTTP_ERRORS.each do |exception_class| described_class::HTTP_ERRORS.each do |exception_class|
...@@ -123,8 +192,8 @@ RSpec.describe Gitlab::HTTP do ...@@ -123,8 +192,8 @@ RSpec.describe Gitlab::HTTP do
context 'with path' do context 'with path' do
before do before do
expect(described_class).to receive(:get) expect(described_class).to receive(:httparty_perform_request)
.with(path, {}) .with(Net::HTTP::Get, path, default_options)
.and_raise(klass) .and_raise(klass)
end end
...@@ -155,8 +224,8 @@ RSpec.describe Gitlab::HTTP do ...@@ -155,8 +224,8 @@ RSpec.describe Gitlab::HTTP do
context 'with path and options' do context 'with path and options' do
before do before do
expect(described_class).to receive(:get) expect(described_class).to receive(:httparty_perform_request)
.with(path, request_options) .with(Net::HTTP::Get, path, request_options)
.and_raise(klass) .and_raise(klass)
end end
...@@ -191,8 +260,8 @@ RSpec.describe Gitlab::HTTP do ...@@ -191,8 +260,8 @@ RSpec.describe Gitlab::HTTP do
end end
before do before do
expect(described_class).to receive(:get) expect(described_class).to receive(:httparty_perform_request)
.with(path, request_options, &block) .with(Net::HTTP::Get, path, request_options, &block)
.and_raise(klass) .and_raise(klass)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment