httpx 1.7.2 → 1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/doc/release_notes/1_7_3.md +29 -0
  4. data/doc/release_notes/1_7_4.md +42 -0
  5. data/doc/release_notes/1_7_5.md +10 -0
  6. data/doc/release_notes/1_7_6.md +24 -0
  7. data/lib/httpx/adapters/datadog.rb +37 -64
  8. data/lib/httpx/adapters/webmock.rb +3 -4
  9. data/lib/httpx/altsvc.rb +4 -2
  10. data/lib/httpx/connection/http1.rb +26 -18
  11. data/lib/httpx/connection/http2.rb +53 -33
  12. data/lib/httpx/connection.rb +152 -63
  13. data/lib/httpx/io/ssl.rb +20 -8
  14. data/lib/httpx/io/tcp.rb +18 -12
  15. data/lib/httpx/io/unix.rb +13 -9
  16. data/lib/httpx/options.rb +23 -7
  17. data/lib/httpx/parser/http1.rb +14 -4
  18. data/lib/httpx/plugins/auth/digest.rb +2 -1
  19. data/lib/httpx/plugins/auth.rb +23 -9
  20. data/lib/httpx/plugins/brotli.rb +33 -5
  21. data/lib/httpx/plugins/cookies/cookie.rb +34 -11
  22. data/lib/httpx/plugins/cookies/jar.rb +93 -18
  23. data/lib/httpx/plugins/cookies.rb +7 -3
  24. data/lib/httpx/plugins/expect.rb +33 -3
  25. data/lib/httpx/plugins/fiber_concurrency.rb +2 -4
  26. data/lib/httpx/plugins/follow_redirects.rb +7 -1
  27. data/lib/httpx/plugins/h2c.rb +1 -1
  28. data/lib/httpx/plugins/proxy/http.rb +15 -8
  29. data/lib/httpx/plugins/proxy.rb +10 -2
  30. data/lib/httpx/plugins/rate_limiter.rb +19 -19
  31. data/lib/httpx/plugins/retries.rb +17 -9
  32. data/lib/httpx/plugins/ssrf_filter.rb +1 -0
  33. data/lib/httpx/plugins/stream_bidi.rb +6 -0
  34. data/lib/httpx/plugins/tracing.rb +137 -0
  35. data/lib/httpx/pool.rb +7 -9
  36. data/lib/httpx/request.rb +15 -3
  37. data/lib/httpx/resolver/multi.rb +1 -8
  38. data/lib/httpx/resolver/native.rb +2 -2
  39. data/lib/httpx/resolver/resolver.rb +21 -2
  40. data/lib/httpx/resolver/system.rb +3 -1
  41. data/lib/httpx/response.rb +5 -1
  42. data/lib/httpx/selector.rb +19 -16
  43. data/lib/httpx/session.rb +34 -44
  44. data/lib/httpx/timers.rb +4 -0
  45. data/lib/httpx/version.rb +1 -1
  46. data/sig/altsvc.rbs +2 -0
  47. data/sig/chainable.rbs +2 -1
  48. data/sig/connection/http1.rbs +3 -1
  49. data/sig/connection/http2.rbs +11 -4
  50. data/sig/connection.rbs +16 -2
  51. data/sig/io/ssl.rbs +1 -0
  52. data/sig/io/tcp.rbs +2 -2
  53. data/sig/options.rbs +8 -3
  54. data/sig/parser/http1.rbs +1 -1
  55. data/sig/plugins/auth.rbs +5 -2
  56. data/sig/plugins/brotli.rbs +11 -6
  57. data/sig/plugins/cookies/cookie.rbs +3 -2
  58. data/sig/plugins/cookies/jar.rbs +11 -0
  59. data/sig/plugins/cookies.rbs +2 -0
  60. data/sig/plugins/expect.rbs +21 -2
  61. data/sig/plugins/fiber_concurrency.rbs +2 -2
  62. data/sig/plugins/proxy/socks4.rbs +4 -0
  63. data/sig/plugins/rate_limiter.rbs +2 -2
  64. data/sig/plugins/response_cache.rbs +3 -3
  65. data/sig/plugins/retries.rbs +17 -13
  66. data/sig/plugins/tracing.rbs +41 -0
  67. data/sig/pool.rbs +1 -1
  68. data/sig/request.rbs +4 -0
  69. data/sig/resolver/native.rbs +2 -0
  70. data/sig/resolver/resolver.rbs +4 -2
  71. data/sig/resolver/system.rbs +0 -2
  72. data/sig/response/body.rbs +1 -1
  73. data/sig/selector.rbs +7 -2
  74. data/sig/session.rbs +2 -0
  75. data/sig/timers.rbs +2 -0
  76. data/sig/transcoder/gzip.rbs +1 -1
  77. data/sig/transcoder.rbs +0 -2
  78. metadata +13 -3
@@ -35,7 +35,10 @@ module HTTPX
35
35
  request.headers["proxy-authorization"] =
36
36
  options.proxy.authenticate(request, response.headers["proxy-authenticate"])
37
37
  send_request(request, selector, options)
38
- return
38
+
39
+ # recalling itself, in case an error was triggered by the above, and we can
40
+ # verify retriability again.
41
+ return fetch_response(request, selector, options)
39
42
  end
40
43
 
41
44
  response
@@ -46,7 +49,7 @@ module HTTPX
46
49
  def force_close(*)
47
50
  if @state == :connecting
48
51
  # proxy connect related requests should not be reenqueed
49
- @parser.reset!
52
+ @parser.reset
50
53
  @inflight -= @parser.pending.size
51
54
  @parser.pending.clear
52
55
  end
@@ -67,18 +70,16 @@ module HTTPX
67
70
  return unless @io.connected?
68
71
 
69
72
  @parser || begin
70
- @parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
71
- parser = @parser
73
+ @parser = parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
72
74
  parser.extend(ProxyParser)
73
75
  parser.on(:response, &method(:__http_on_connect))
74
76
  parser.on(:close) do
75
77
  next unless @parser
76
78
 
77
79
  reset
78
- disconnect
79
80
  end
80
81
  parser.on(:reset) do
81
- if parser.empty?
82
+ if parser.pending.empty? && parser.empty?
82
83
  reset
83
84
  else
84
85
  enqueue_pending_requests_from_parser(parser)
@@ -94,17 +95,23 @@ module HTTPX
94
95
  # keep parser state around due to proxy auth protocol;
95
96
  # intermediate authenticated request is already inside
96
97
  # the parser
97
- parser = nil
98
+ connect_request = parser = nil
98
99
 
99
100
  if initial_state == :connecting
100
101
  parser = @parser
101
102
  @parser.reset
103
+ if @pending.first.is_a?(ConnectRequest)
104
+ connect_request = @pending.shift # this happened when reenqueing
105
+ end
102
106
  end
103
107
 
104
108
  idling
105
109
 
106
110
  @parser = parser
107
-
111
+ if connect_request
112
+ @inflight += 1
113
+ parser.send(connect_request)
114
+ end
108
115
  transition(:connecting)
109
116
  end
110
117
  end
@@ -202,7 +202,10 @@ module HTTPX
202
202
  log { "failed connecting to proxy, trying next..." }
203
203
  request.transition(:idle)
204
204
  send_request(request, selector, options)
205
- return
205
+
206
+ # recalling itself, in case an error was triggered by the above, and we can
207
+ # verify retriability again.
208
+ return fetch_response(request, selector, options)
206
209
  end
207
210
  response
208
211
  rescue ProxyError
@@ -320,7 +323,12 @@ module HTTPX
320
323
 
321
324
  def purge_after_closed
322
325
  super
323
- @io = @io.proxy_io if @io.respond_to?(:proxy_io)
326
+
327
+ while @io.respond_to?(:proxy_io)
328
+ @io = @io.proxy_io
329
+
330
+ super
331
+ end
324
332
  end
325
333
  end
326
334
 
@@ -16,25 +16,7 @@ module HTTPX
16
16
 
17
17
  class << self
18
18
  def load_dependencies(klass)
19
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
20
- end
21
-
22
- # Servers send the "Retry-After" header field to indicate how long the
23
- # user agent ought to wait before making a follow-up request. When
24
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
25
- # how long the service is expected to be unavailable to the client.
26
- # When sent with any 3xx (Redirection) response, Retry-After indicates
27
- # the minimum time that the user agent is asked to wait before issuing
28
- # the redirected request.
29
- #
30
- def retry_after_rate_limit(_, response)
31
- return unless response.is_a?(Response)
32
-
33
- retry_after = response.headers["retry-after"]
34
-
35
- return unless retry_after
36
-
37
- Utils.parse_retry_after(retry_after)
19
+ klass.plugin(:retries)
38
20
  end
39
21
  end
40
22
 
@@ -52,6 +34,24 @@ module HTTPX
52
34
  def rate_limit_error?(response)
53
35
  response.is_a?(Response) && RATE_LIMIT_CODES.include?(response.status)
54
36
  end
37
+
38
+ # Servers send the "Retry-After" header field to indicate how long the
39
+ # user agent ought to wait before making a follow-up request. When
40
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
41
+ # how long the service is expected to be unavailable to the client.
42
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
43
+ # the minimum time that the user agent is asked to wait before issuing
44
+ # the redirected request.
45
+ #
46
+ def when_to_retry(_, response, options)
47
+ return super unless response.is_a?(Response)
48
+
49
+ retry_after = response.headers["retry-after"]
50
+
51
+ return super unless retry_after
52
+
53
+ Utils.parse_retry_after(retry_after)
54
+ end
55
55
  end
56
56
  end
57
57
 
@@ -148,10 +148,7 @@ module HTTPX
148
148
  log { "failed to get response, #{request.retries} tries to go..." }
149
149
  prepare_to_retry(request, response)
150
150
 
151
- retry_after = options.retry_after
152
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
153
-
154
- if retry_after
151
+ if (retry_after = when_to_retry(request, response, options)) && retry_after.positive?
155
152
  # apply jitter
156
153
  if (jitter = request.options.retry_jitter)
157
154
  retry_after = jitter.call(retry_after)
@@ -169,11 +166,15 @@ module HTTPX
169
166
  send_request(request, selector, options)
170
167
  end
171
168
  end
169
+
170
+ return
172
171
  else
173
172
  send_request(request, selector, options)
174
- end
175
173
 
176
- return
174
+ # recalling itself, in case an error was triggered by the above, and we can
175
+ # verify retriability again.
176
+ return fetch_response(request, selector, options)
177
+ end
177
178
  end
178
179
  response
179
180
  end
@@ -201,6 +202,12 @@ module HTTPX
201
202
  request.transition(:idle)
202
203
  end
203
204
 
205
+ def when_to_retry(request, response, options)
206
+ retry_after = options.retry_after
207
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
208
+ retry_after
209
+ end
210
+
204
211
  #
205
212
  # Attempt to set the request to perform a partial range request.
206
213
  # This happens if the peer server accepts byte-range requests, and
@@ -237,14 +244,15 @@ module HTTPX
237
244
  def initialize(*args)
238
245
  super
239
246
  @retries = @options.max_retries
247
+ @partial_response = nil
240
248
  end
241
249
 
242
250
  def response=(response)
243
- if @partial_response
251
+ if (partial_response = @partial_response)
244
252
  if response.is_a?(Response) && response.status == 206
245
- response.from_partial_response(@partial_response)
253
+ response.from_partial_response(partial_response)
246
254
  else
247
- @partial_response.close
255
+ partial_response.close
248
256
  end
249
257
  @partial_response = nil
250
258
  end
@@ -106,6 +106,7 @@ module HTTPX
106
106
  error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
107
107
  error.set_backtrace(caller)
108
108
  response = ErrorResponse.new(request, error)
109
+ request.response = response
109
110
  request.emit(:response, response)
110
111
  response
111
112
  end
@@ -189,6 +189,10 @@ module HTTPX
189
189
  !@closed
190
190
  end
191
191
 
192
+ def force_close(*)
193
+ terminate
194
+ end
195
+
192
196
  def terminate
193
197
  return if @closed
194
198
 
@@ -202,6 +206,8 @@ module HTTPX
202
206
  terminate
203
207
  end
204
208
 
209
+ alias_method :on_io_error, :on_error
210
+
205
211
  # noop (the owner connection will take of it)
206
212
  def handle_socket_timeout(interval); end
207
213
  end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX::Plugins
4
+ #
5
+ # This plugin adds a simple interface to integrate request tracing SDKs.
6
+ #
7
+ # An example of such an integration is the datadog adapter.
8
+ #
9
+ # https://gitlab.com/os85/httpx/wikis/Tracing
10
+ #
11
+ module Tracing
12
+ class Wrapper
13
+ attr_reader :tracers
14
+ protected :tracers
15
+
16
+ def initialize(*tracers)
17
+ @tracers = tracers.flat_map do |tracer|
18
+ case tracer
19
+ when Wrapper
20
+ tracer.tracers
21
+ else
22
+ tracer
23
+ end
24
+ end.uniq
25
+ end
26
+
27
+ def merge(tracer)
28
+ case tracer
29
+ when Wrapper
30
+ Wrapper.new(*@tracers, *tracer.tracers)
31
+ else
32
+ Wrapper.new(*@tracers, tracer)
33
+ end
34
+ end
35
+
36
+ def freeze
37
+ @tracers.each(&:freeze).freeze
38
+ super
39
+ end
40
+
41
+ %i[start finish reset enabled?].each do |callback|
42
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
43
+ # proxies ##{callback} calls to wrapper tracers.
44
+ def #{callback}(*args) # def start(*args)
45
+ @tracers.each { |t| t.#{callback}(*args) } # @tracers.each { |t| t.start(*args) }
46
+ end # end
47
+ OUT
48
+ end
49
+ end
50
+
51
+ # adds support for the following options:
52
+ #
53
+ # :tracer :: object which responds to #start, #finish and #reset.
54
+ module OptionsMethods
55
+ private
56
+
57
+ def option_tracer(tracer)
58
+ unless tracer.respond_to?(:start) &&
59
+ tracer.respond_to?(:finish) &&
60
+ tracer.respond_to?(:reset) &&
61
+ tracer.respond_to?(:enabled?)
62
+ raise TypeError, "#{tracer} must to respond to `#start(r)`, `#finish` and `#reset` and `#enabled?"
63
+ end
64
+
65
+ tracer = Wrapper.new(@tracer, tracer) if @tracer
66
+ tracer
67
+ end
68
+ end
69
+
70
+ module RequestMethods
71
+ attr_accessor :init_time
72
+
73
+ # intercepts request initialization to inject the tracing logic.
74
+ def initialize(*)
75
+ super
76
+
77
+ @init_time = nil
78
+
79
+ tracer = @options.tracer
80
+
81
+ return unless tracer && tracer.enabled?(self)
82
+
83
+ on(:idle) do
84
+ tracer.reset(self)
85
+
86
+ # request is reset when it's retried.
87
+ @init_time = nil
88
+ end
89
+ on(:headers) do
90
+ # the usual request init time (when not including the connection handshake)
91
+ # should be the time the request is buffered the first time.
92
+ @init_time ||= ::Time.now.utc
93
+
94
+ tracer.start(self)
95
+ end
96
+ on(:response) { |response| tracer.finish(self, response) }
97
+ end
98
+
99
+ def response=(*)
100
+ # init_time should be set when it's send to a connection.
101
+ # However, there are situations where connection initialization fails.
102
+ # Example is the :ssrf_filter plugin, which raises an error on
103
+ # initialize if the host is an IP which matches against the known set.
104
+ # in such cases, we'll just set here right here.
105
+ @init_time ||= ::Time.now.utc
106
+
107
+ super
108
+ end
109
+ end
110
+
111
+ # Connection mixin
112
+ module ConnectionMethods
113
+ def initialize(*)
114
+ super
115
+
116
+ @init_time = ::Time.now.utc
117
+ end
118
+
119
+ def send(request)
120
+ # request init time is only the same as the connection init time
121
+ # if the connection is going through the connection handshake.
122
+ request.init_time ||= @init_time unless open?
123
+
124
+ super
125
+ end
126
+
127
+ def idling
128
+ super
129
+
130
+ # time of initial request(s) is accounted from the moment
131
+ # the connection is back to :idle, and ready to connect again.
132
+ @init_time = ::Time.now.utc
133
+ end
134
+ end
135
+ end
136
+ register_plugin :tracing, Tracing
137
+ end
data/lib/httpx/pool.rb CHANGED
@@ -122,12 +122,6 @@ module HTTPX
122
122
 
123
123
  @max_connections_cond.signal
124
124
  @origin_conds[connection.origin.to_s].signal
125
-
126
- # Observed situations where a session handling multiple requests in a loop
127
- # across multiple threads checks the same connection in and out, while another
128
- # thread which is waiting on the same connection never gets the chance to pick
129
- # it up, because ruby's thread scheduler never switched on to it in the process.
130
- Thread.pass
131
125
  end
132
126
  end
133
127
 
@@ -153,16 +147,20 @@ module HTTPX
153
147
  resolvers = @resolvers[resolver_type]
154
148
 
155
149
  idx = resolvers.find_index do |res|
156
- res.options == options
150
+ res.options.resolver_options_match?(options)
157
151
  end
158
152
  resolvers.delete_at(idx) if idx
159
153
  end || checkout_new_resolver(resolver_type, options)
160
154
  end
161
155
 
162
156
  def checkin_resolver(resolver)
163
- resolver_class = resolver.class
157
+ if resolver.is_a?(Resolver::Multi)
158
+ resolver_class = resolver.resolvers.first.class
159
+ else
160
+ resolver_class = resolver.class
164
161
 
165
- resolver = resolver.multi
162
+ resolver = resolver.multi
163
+ end
166
164
 
167
165
  # a multi requires all sub-resolvers being closed in order to be
168
166
  # correctly checked back in.
data/lib/httpx/request.rb CHANGED
@@ -42,6 +42,9 @@ module HTTPX
42
42
  # The IP address from the peer server.
43
43
  attr_accessor :peer_address
44
44
 
45
+ # the connection the request is currently being sent to (none if before or after transaction)
46
+ attr_writer :connection
47
+
45
48
  attr_writer :persistent
46
49
 
47
50
  attr_reader :active_timeouts
@@ -91,7 +94,7 @@ module HTTPX
91
94
  raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
92
95
 
93
96
  @state = :idle
94
- @response = @peer_address = @informational_status = nil
97
+ @connection = @response = @drainer = @peer_address = @informational_status = nil
95
98
  @ping = false
96
99
  @persistent = @options.persistent
97
100
  @active_timeouts = []
@@ -274,8 +277,7 @@ module HTTPX
274
277
  when :idle
275
278
  @body.rewind
276
279
  @ping = false
277
- @response = nil
278
- @drainer = nil
280
+ @response = @drainer = nil
279
281
  @active_timeouts.clear
280
282
  when :headers
281
283
  return unless @state == :idle
@@ -321,6 +323,16 @@ module HTTPX
321
323
  callbacks(event).delete(clb)
322
324
  end
323
325
  end
326
+
327
+ def handle_error(error)
328
+ if (connection = @connection)
329
+ connection.on_error(error, self)
330
+ else
331
+ response = ErrorResponse.new(self, error)
332
+ self.response = response
333
+ emit(:response, response)
334
+ end
335
+ end
324
336
  end
325
337
  end
326
338
 
@@ -71,14 +71,7 @@ module HTTPX
71
71
 
72
72
  def lazy_resolve(connection)
73
73
  @resolvers.each do |resolver|
74
- conn_to_resolve = @current_session.try_clone_connection(connection, @current_selector, resolver.family)
75
- resolver << conn_to_resolve
76
-
77
- next if resolver.empty?
78
-
79
- # both the resolver and the connection it's resolving must be pineed to the session
80
- @current_session.pin(conn_to_resolve, @current_selector)
81
- @current_session.select_resolver(resolver, @current_selector)
74
+ resolver.lazy_resolve(connection)
82
75
  end
83
76
  end
84
77
 
@@ -66,7 +66,7 @@ module HTTPX
66
66
  end
67
67
 
68
68
  def closed?
69
- @state == :closed
69
+ @state == :idle || @state == :closed
70
70
  end
71
71
 
72
72
  def to_io
@@ -529,7 +529,7 @@ module HTTPX
529
529
 
530
530
  resolve if @queries.empty? && !@connections.empty?
531
531
  when :closed
532
- return unless @state == :open
532
+ return if @state == :closed
533
533
 
534
534
  @io.close if @io
535
535
  @start_timeout = nil
@@ -119,6 +119,11 @@ module HTTPX
119
119
  end
120
120
  end
121
121
 
122
+ def on_io_error(e)
123
+ on_error(e)
124
+ force_close(true)
125
+ end
126
+
122
127
  def on_error(error)
123
128
  handle_error(error)
124
129
  disconnect
@@ -138,6 +143,19 @@ module HTTPX
138
143
  true
139
144
  end
140
145
 
146
+ def lazy_resolve(connection)
147
+ return unless @current_session && @current_selector
148
+
149
+ conn_to_resolve = @current_session.try_clone_connection(connection, @current_selector, @family)
150
+ self << conn_to_resolve
151
+
152
+ return if empty?
153
+
154
+ # both the resolver and the connection it's resolving must be pinned to the session
155
+ @current_session.pin(conn_to_resolve, @current_selector)
156
+ @current_session.select_resolver(self, @current_selector)
157
+ end
158
+
141
159
  private
142
160
 
143
161
  def emit_resolved_connection(connection, addresses, early_resolve)
@@ -181,9 +199,10 @@ module HTTPX
181
199
  end
182
200
 
183
201
  def disconnect
184
- return if closed?
185
-
186
202
  close
203
+
204
+ return unless closed?
205
+
187
206
  @current_session.deselect_resolver(self, @current_selector)
188
207
  end
189
208
  end
@@ -116,7 +116,9 @@ module HTTPX
116
116
  @current_session.select_resolver(self, @current_selector)
117
117
  end
118
118
 
119
- def early_resolve(connection, **); end
119
+ def early_resolve(_, **) # rubocop:disable Naming/PredicateMethod
120
+ false
121
+ end
120
122
 
121
123
  def handle_socket_timeout(interval)
122
124
  error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
@@ -112,6 +112,7 @@ module HTTPX
112
112
  def finish!
113
113
  @finished = true
114
114
  @headers.freeze
115
+ @request.connection = nil
115
116
  end
116
117
 
117
118
  # returns whether the response contains body payload.
@@ -282,6 +283,7 @@ module HTTPX
282
283
  @error = error
283
284
  @options = request.options
284
285
  log_exception(@error)
286
+ finish!
285
287
  end
286
288
 
287
289
  # returns the exception full message.
@@ -299,7 +301,9 @@ module HTTPX
299
301
  true
300
302
  end
301
303
 
302
- def finish!; end
304
+ def finish!
305
+ @request.connection = nil
306
+ end
303
307
 
304
308
  # raises the wrapped exception.
305
309
  def raise_for_status
@@ -29,7 +29,7 @@ module HTTPX
29
29
 
30
30
  def_delegator :@timers, :after
31
31
 
32
- def_delegator :@selectables, :empty?
32
+ def_delegator :@selectables, :each
33
33
 
34
34
  def initialize
35
35
  @timers = Timers.new
@@ -37,8 +37,8 @@ module HTTPX
37
37
  @is_timer_interval = false
38
38
  end
39
39
 
40
- def each(&blk)
41
- @selectables.each(&blk)
40
+ def empty?
41
+ @selectables.empty? && @timers.empty?
42
42
  end
43
43
 
44
44
  def next_tick
@@ -81,7 +81,8 @@ module HTTPX
81
81
 
82
82
  def find_resolver(options)
83
83
  res = @selectables.find do |c|
84
- c.is_a?(Resolver::Resolver) && options == c.options
84
+ c.is_a?(Resolver::Resolver) &&
85
+ options.resolver_options_match?(c.options)
85
86
  end
86
87
 
87
88
  res.multi if res
@@ -148,16 +149,18 @@ module HTTPX
148
149
 
149
150
  next(is_closed) if is_closed
150
151
 
151
- io.log(level: 2) do
152
- "[#{io.state}] registering in selector##{object_id} for select (#{interests})#{" for #{interval} seconds" unless interval.nil?}"
153
- end
152
+ if interests
153
+ io.log(level: 2) do
154
+ "[#{io.state}] registering in selector##{object_id} for select (#{interests})#{" for #{interval} seconds" unless interval.nil?}"
155
+ end
154
156
 
155
- if READABLE.include?(interests)
156
- r = r.nil? ? io : (Array(r) << io)
157
- end
157
+ if READABLE.include?(interests)
158
+ r = r.nil? ? io : (Array(r) << io)
159
+ end
158
160
 
159
- if WRITABLE.include?(interests)
160
- w = w.nil? ? io : (Array(w) << io)
161
+ if WRITABLE.include?(interests)
162
+ w = w.nil? ? io : (Array(w) << io)
163
+ end
161
164
  end
162
165
 
163
166
  is_closed
@@ -204,8 +207,7 @@ module HTTPX
204
207
  rescue IOError => e
205
208
  (Array(r) + Array(w)).each do |sel|
206
209
  # TODO: is there a way to cheaply find the IO associated with the error?
207
- sel.on_error(e)
208
- sel.force_close(true)
210
+ sel.on_io_error(e)
209
211
  end
210
212
  rescue StandardError => e
211
213
  (Array(r) + Array(w)).each do |sel|
@@ -249,8 +251,9 @@ module HTTPX
249
251
  when :rw then rw_wait(io, interval)
250
252
  end
251
253
  rescue IOError => e
252
- io.on_error(e)
253
- io.force_close(true)
254
+ io.on_io_error(e)
255
+
256
+ return
254
257
  rescue StandardError => e
255
258
  io.on_error(e)
256
259