Skip to content

Commit b4fe926

Browse files
committed
Refactor retriable from separate class hierarchy to session option
Make retriable a first-class option on HTTP::Options (like `follow`), eliminating the need for Retriable::Client and Retriable::Session. Client#perform now checks options.retriable and delegates to Retriable::Performer when set, keeping retry as a per-request concern that flows naturally through the options system.
1 parent 8e9538a commit b4fe926

13 files changed

Lines changed: 81 additions & 223 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040
calls HTTP verb methods (`.get`, `.post`, etc.) or accesses `.default_options`
4141
is unaffected. Code that checks `is_a?(HTTP::Client)` on the return value of
4242
chainable methods will need to be updated to check for `HTTP::Session`
43-
- **BREAKING** `.retriable` now returns `HTTP::Retriable::Session` instead of
44-
`HTTP::Retriable::Client`
43+
- **BREAKING** `.retriable` now returns `HTTP::Session` instead of
44+
`HTTP::Retriable::Client`. Retry is a session-level option: it flows through
45+
`HTTP::Options` into `HTTP::Client#perform`, eliminating the need for
46+
separate `Retriable::Client` and `Retriable::Session` classes
4547
- Improved error message when request body size cannot be determined to suggest
4648
setting `Content-Length` explicitly or using chunked `Transfer-Encoding` (#560)
4749
- **BREAKING** `Connection#readpartial` now raises `EOFError` instead of

lib/http.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
require "http/chainable"
88
require "http/session"
99
require "http/client"
10-
require "http/retriable/client"
11-
require "http/retriable/session"
1210
require "http/connection"
1311
require "http/options"
1412
require "http/feature"

lib/http/chainable.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,10 @@ def use(*features)
289289
# HTTP.retriable(tries: 3, delay: proc { |i| 1 + i*i }).get(url)
290290
#
291291
# @param (see Performer#initialize)
292-
# @return [HTTP::Retriable::Session]
292+
# @return [HTTP::Session]
293293
# @api public
294294
def retriable(**options)
295-
Retriable::Session.new(Retriable::Performer.new(options), default_options)
295+
branch default_options.with_retriable(options.empty? || options)
296296
end
297297

298298
private

lib/http/client.rb

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
require "http/client/request_builder"
66
require "http/form_data"
7+
require "http/retriable/performer"
78
require "http/options"
89
require "http/feature"
910
require "http/headers"
@@ -105,6 +106,35 @@ def build_request(verb, uri, opts = {})
105106
# @return [HTTP::Response] the response
106107
# @api public
107108
def perform(req, options)
109+
if options.retriable
110+
perform_with_retry(req, options)
111+
else
112+
perform_once(req, options)
113+
end
114+
end
115+
116+
# Close the connection and reset state
117+
#
118+
# @example
119+
# client.close
120+
#
121+
# @return [void]
122+
# @api public
123+
def close
124+
@connection&.close
125+
@connection = nil
126+
@state = :clean
127+
end
128+
129+
private
130+
131+
# Execute a single HTTP request without retry logic
132+
#
133+
# @param req [HTTP::Request] the request to perform
134+
# @param options [HTTP::Options] request options
135+
# @return [HTTP::Response] the response
136+
# @api private
137+
def perform_once(req, options)
108138
verify_connection!(req.uri)
109139

110140
@state = :dirty
@@ -121,21 +151,18 @@ def perform(req, options)
121151
raise
122152
end
123153

124-
# Close the connection and reset state
125-
#
126-
# @example
127-
# client.close
154+
# Execute a request with retry logic
128155
#
129-
# @return [void]
130-
# @api public
131-
def close
132-
@connection&.close
133-
@connection = nil
134-
@state = :clean
156+
# @param req [HTTP::Request] the request to perform
157+
# @param options [HTTP::Options] request options
158+
# @return [HTTP::Response] the response
159+
# @api private
160+
def perform_with_retry(req, options)
161+
Retriable::Performer.new(options.retriable).perform(self, req) do
162+
perform_once(req, options)
163+
end
135164
end
136165

137-
private
138-
139166
# Send request over the connection, handling proxy and errors
140167
# @return [void]
141168
# @api private

lib/http/options/definitions.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,22 @@ def follow=(value)
8181
end
8282
end
8383

84+
def_option :retriable, reader_only: true
85+
86+
# Sets retriable options
87+
#
88+
# @param [Boolean, Hash, nil] value
89+
# @api private
90+
# @return [Hash, nil]
91+
def retriable=(value)
92+
@retriable =
93+
if !value then nil
94+
elsif true == value then Hash[]
95+
elsif value.respond_to?(:fetch) then value
96+
else argument_error! "Unsupported retriable options: #{value}"
97+
end
98+
end
99+
84100
def_option :persistent, reader_only: true
85101

86102
# Sets persistent connection origin

lib/http/retriable/client.rb

Lines changed: 0 additions & 61 deletions
This file was deleted.

lib/http/retriable/session.rb

Lines changed: 0 additions & 51 deletions
This file was deleted.

sig/http.rbs

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ module HTTP
5252
def default_options=: (untyped opts) -> Options
5353
def nodelay: () -> Session
5454
def use: (*untyped features) -> Session
55-
def retriable: (**untyped options) -> Retriable::Session
55+
def retriable: (**untyped options) -> Session
5656

5757
private
5858

@@ -83,6 +83,8 @@ module HTTP
8383

8484
private
8585

86+
def perform_once: (Request req, Options options) -> Response
87+
def perform_with_retry: (Request req, Options options) -> Response
8688
def wrap_request: (Request req, Options opts) -> Request
8789
def build_response: (Request req, Options options) -> Response
8890
def build_wrapped_response: (Request req, Options options) -> Response
@@ -428,6 +430,7 @@ module HTTP
428430
attr_reader features: Hash[Symbol, Feature]
429431

430432
def follow=: (untyped value) -> untyped
433+
def retriable=: (untyped value) -> untyped
431434
def persistent=: (untyped value) -> String?
432435
def persistent?: () -> bool
433436
def merge: (untyped other) -> Options
@@ -454,13 +457,15 @@ module HTTP
454457
attr_accessor cookies: Hash[untyped, untyped]
455458
attr_accessor encoding: Encoding?
456459
attr_reader follow: Hash[untyped, untyped]?
460+
attr_reader retriable: Hash[untyped, untyped]?
457461
attr_reader persistent: String?
458462

459463
def with_headers: (untyped value) -> Options
460464
def with_cookies: (untyped value) -> Options
461465
def with_encoding: (untyped value) -> Options
462466
def with_features: (untyped value) -> Options
463467
def with_follow: (untyped value) -> Options
468+
def with_retriable: (untyped value) -> Options
464469
def with_persistent: (untyped value) -> Options
465470
def with_proxy: (untyped value) -> Options
466471
def with_params: (untyped value) -> Options
@@ -1059,29 +1064,6 @@ module HTTP
10591064
end
10601065

10611066
module Retriable
1062-
class Session < HTTP::Session
1063-
@performer: untyped
1064-
1065-
def initialize: (untyped performer, untyped options) -> void
1066-
1067-
private
1068-
1069-
def branch: (Options options) -> Retriable::Session
1070-
def make_client: (Options options) -> Retriable::Client
1071-
end
1072-
1073-
class Client < HTTP::Client
1074-
@performer: untyped
1075-
1076-
def initialize: (untyped performer, untyped options) -> void
1077-
def perform: (Request req, Options options) -> Response
1078-
1079-
private
1080-
1081-
def branch: (Options options) -> Retriable::Session
1082-
def make_client: (Options options) -> Retriable::Client
1083-
end
1084-
10851067
class Performer
10861068
RETRIABLE_ERRORS: Array[untyped]
10871069

test/http/options/merge_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
headers: { "Foo" => "foo", "Accept" => "xml", "Bar" => "bar" },
6363
proxy: { proxy_address: "127.0.0.1", proxy_port: 8080 },
6464
follow: nil,
65+
retriable: nil,
6566
socket_class: HTTP::Options.default_socket_class,
6667
nodelay: false,
6768
ssl_socket_class: HTTP::Options.default_ssl_socket_class,

test/http/options_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
end
3030
end
3131

32+
describe "#with_retriable" do
33+
it "raises error for unsupported retriable options" do
34+
err = assert_raises(HTTP::Error) { subject_under_test.with_retriable(42) }
35+
assert_match(/Unsupported retriable/, err.message)
36+
end
37+
end
38+
3239
describe "#dup" do
3340
it "returns a duplicate without a block" do
3441
dupped = subject_under_test.dup

0 commit comments

Comments
 (0)