diff --git a/lib/httpx/plugins/retries.rb b/lib/httpx/plugins/retries.rb index a6043a24..1c182669 100644 --- a/lib/httpx/plugins/retries.rb +++ b/lib/httpx/plugins/retries.rb @@ -25,6 +25,17 @@ module HTTPX def self.extra_options(options) Class.new(options.class) do + # number of seconds after which one can retry the request + def_option(:retry_after) do |num| + # return early if callable + return num if num.respond_to?(:call) + + num = Integer(num) + raise Error, ":retry_after must be positive" unless num.positive? + + num + end + def_option(:max_retries) do |num| num = Integer(num) raise Error, ":max_retries must be positive" unless num.positive? @@ -63,8 +74,7 @@ module HTTPX log { "failed to get response, #{request.retries} tries to go..." } request.transition(:idle) connection = find_connection(request, connections, options) - connection.send(request) - set_request_timeout(connection, request, options) + __retry_request(connection, request, options) return end response @@ -77,6 +87,23 @@ module HTTPX def __retryable_error?(ex) RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) } end + + def __retry_request(connection, request, options) + retry_after = options.retry_after + unless retry_after + connection.send(request) + set_request_timeout(connection, request, options) + return + end + + retry_after = retry_after.call(request) if retry_after.respond_to?(:call) + log { "retrying after #{retry_after} secs..." } + pool.after(retry_after) do + log { "retrying!!" } + connection.send(request) + set_request_timeout(connection, request, options) + end + end end module RequestMethods diff --git a/test/support/requests/plugins/retries.rb b/test/support/requests/plugins/retries.rb index 70bbe7f3..040a1418 100644 --- a/test/support/requests/plugins/retries.rb +++ b/test/support/requests/plugins/retries.rb @@ -46,6 +46,38 @@ module Requests assert retries_session.calls.zero?, "expect request not to be retried (it was, #{retries_session.calls} times)" end + def test_plugin_retries_retry_after + before_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) + retries_session = HTTPX + .plugin(RequestInspector) + .plugin(:retries, retry_after: 2) + .timeout(total_timeout: 3) + .max_retries(1) + retries_response = retries_session.get(build_uri("/delay/10")) + after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) + total_time = after_time - before_time + + assert retries_response.is_a?(HTTPX::ErrorResponse) + assert_in_delta 3 + 2 + 3, total_time, 1, "request didn't take as expected to retry (#{total_time} secs)" + end + + def test_plugin_retries_retry_after_callable + retries = 0 + exponential = ->(_) { (retries += 1) * 2 } + before_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) + retries_session = HTTPX + .plugin(RequestInspector) + .plugin(:retries, retry_after: exponential) + .timeout(total_timeout: 3) + .max_retries(2) + retries_response = retries_session.get(build_uri("/delay/10")) + after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) + total_time = after_time - before_time + + assert retries_response.is_a?(HTTPX::ErrorResponse) + assert_in_delta 3 + 2 + 3 + 4 + 3, total_time, 1, "request didn't take as expected to retry (#{total_time} secs)" + end + module RequestInspector module InstanceMethods attr_reader :calls