httpx 1.4.4 → 1.5.0

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_5_0.md +126 -0
  3. data/lib/httpx/adapters/datadog.rb +24 -3
  4. data/lib/httpx/adapters/webmock.rb +1 -0
  5. data/lib/httpx/buffer.rb +16 -5
  6. data/lib/httpx/connection/http1.rb +8 -9
  7. data/lib/httpx/connection/http2.rb +48 -24
  8. data/lib/httpx/connection.rb +36 -19
  9. data/lib/httpx/errors.rb +2 -11
  10. data/lib/httpx/headers.rb +24 -23
  11. data/lib/httpx/io/ssl.rb +2 -1
  12. data/lib/httpx/io/tcp.rb +9 -7
  13. data/lib/httpx/io/unix.rb +1 -1
  14. data/lib/httpx/loggable.rb +13 -1
  15. data/lib/httpx/options.rb +63 -48
  16. data/lib/httpx/parser/http1.rb +1 -1
  17. data/lib/httpx/plugins/aws_sigv4.rb +1 -0
  18. data/lib/httpx/plugins/callbacks.rb +19 -6
  19. data/lib/httpx/plugins/circuit_breaker.rb +4 -3
  20. data/lib/httpx/plugins/cookies/jar.rb +0 -2
  21. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +7 -4
  22. data/lib/httpx/plugins/cookies.rb +4 -4
  23. data/lib/httpx/plugins/follow_redirects.rb +4 -2
  24. data/lib/httpx/plugins/grpc/call.rb +1 -1
  25. data/lib/httpx/plugins/h2c.rb +7 -1
  26. data/lib/httpx/plugins/persistent.rb +22 -1
  27. data/lib/httpx/plugins/proxy/http.rb +3 -1
  28. data/lib/httpx/plugins/query.rb +35 -0
  29. data/lib/httpx/plugins/response_cache/file_store.rb +115 -15
  30. data/lib/httpx/plugins/response_cache/store.rb +7 -67
  31. data/lib/httpx/plugins/response_cache.rb +179 -29
  32. data/lib/httpx/plugins/retries.rb +26 -14
  33. data/lib/httpx/plugins/stream.rb +4 -2
  34. data/lib/httpx/plugins/stream_bidi.rb +315 -0
  35. data/lib/httpx/pool.rb +58 -5
  36. data/lib/httpx/request/body.rb +1 -1
  37. data/lib/httpx/request.rb +6 -2
  38. data/lib/httpx/resolver/https.rb +10 -4
  39. data/lib/httpx/resolver/native.rb +13 -13
  40. data/lib/httpx/resolver/resolver.rb +4 -0
  41. data/lib/httpx/resolver/system.rb +37 -14
  42. data/lib/httpx/resolver.rb +2 -2
  43. data/lib/httpx/response/body.rb +10 -21
  44. data/lib/httpx/response/buffer.rb +36 -12
  45. data/lib/httpx/response.rb +11 -1
  46. data/lib/httpx/selector.rb +16 -12
  47. data/lib/httpx/session.rb +79 -19
  48. data/lib/httpx/timers.rb +24 -16
  49. data/lib/httpx/transcoder/multipart/decoder.rb +4 -2
  50. data/lib/httpx/transcoder/multipart/encoder.rb +2 -1
  51. data/lib/httpx/version.rb +1 -1
  52. data/sig/buffer.rbs +1 -1
  53. data/sig/chainable.rbs +5 -2
  54. data/sig/connection/http2.rbs +11 -2
  55. data/sig/connection.rbs +4 -4
  56. data/sig/errors.rbs +0 -3
  57. data/sig/headers.rbs +15 -10
  58. data/sig/httpx.rbs +5 -1
  59. data/sig/io/tcp.rbs +6 -0
  60. data/sig/loggable.rbs +2 -0
  61. data/sig/options.rbs +7 -1
  62. data/sig/plugins/cookies/cookie.rbs +1 -3
  63. data/sig/plugins/cookies/jar.rbs +4 -4
  64. data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
  65. data/sig/plugins/cookies.rbs +2 -0
  66. data/sig/plugins/h2c.rbs +4 -0
  67. data/sig/plugins/proxy/http.rbs +3 -0
  68. data/sig/plugins/proxy.rbs +4 -0
  69. data/sig/plugins/query.rbs +18 -0
  70. data/sig/plugins/response_cache/file_store.rbs +19 -0
  71. data/sig/plugins/response_cache/store.rbs +13 -0
  72. data/sig/plugins/response_cache.rbs +41 -19
  73. data/sig/plugins/retries.rbs +4 -3
  74. data/sig/plugins/stream.rbs +5 -1
  75. data/sig/plugins/stream_bidi.rbs +68 -0
  76. data/sig/plugins/upgrade/h2.rbs +9 -0
  77. data/sig/plugins/upgrade.rbs +5 -0
  78. data/sig/pool.rbs +5 -0
  79. data/sig/punycode.rbs +5 -0
  80. data/sig/request.rbs +2 -0
  81. data/sig/resolver/https.rbs +3 -2
  82. data/sig/resolver/native.rbs +1 -2
  83. data/sig/resolver/resolver.rbs +11 -3
  84. data/sig/resolver/system.rbs +19 -2
  85. data/sig/resolver.rbs +11 -7
  86. data/sig/response/body.rbs +3 -4
  87. data/sig/response/buffer.rbs +2 -3
  88. data/sig/response.rbs +2 -2
  89. data/sig/selector.rbs +20 -10
  90. data/sig/session.rbs +14 -6
  91. data/sig/timers.rbs +5 -7
  92. data/sig/transcoder/multipart.rbs +4 -3
  93. metadata +13 -2
@@ -2,6 +2,8 @@
2
2
 
3
3
  module HTTPX
4
4
  class StreamResponse
5
+ attr_reader :request
6
+
5
7
  def initialize(request, session)
6
8
  @request = request
7
9
  @options = @request.options
@@ -71,7 +73,7 @@ module HTTPX
71
73
 
72
74
  # :nocov:
73
75
  def inspect
74
- "#<StreamResponse:#{object_id}>"
76
+ "#<#{self.class}:#{object_id}>"
75
77
  end
76
78
  # :nocov:
77
79
 
@@ -114,7 +116,7 @@ module HTTPX
114
116
 
115
117
  module Plugins
116
118
  #
117
- # This plugin adds support for stream response (text/event-stream).
119
+ # This plugin adds support for streaming a response (useful for i.e. "text/event-stream" payloads).
118
120
  #
119
121
  # https://gitlab.com/os85/httpx/wikis/Stream
120
122
  #
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for bidirectional HTTP/2 streams.
7
+ #
8
+ # https://gitlab.com/os85/httpx/wikis/StreamBidi
9
+ #
10
+ # It is required that the request body allows chunk to be buffered, (i.e., responds to +#<<(chunk)+).
11
+ module StreamBidi
12
+ # Extension of the Connection::HTTP2 class, which adds functionality to
13
+ # deal with a request that can't be drained and must be interleaved with
14
+ # the response streams.
15
+ #
16
+ # The streams keeps send DATA frames while there's data; when they're ain't,
17
+ # the stream is kept open; it must be explicitly closed by the end user.
18
+ #
19
+ class HTTP2Bidi < Connection::HTTP2
20
+ def initialize(*)
21
+ super
22
+ @lock = Thread::Mutex.new
23
+ end
24
+
25
+ %i[close empty? exhausted? send <<].each do |lock_meth|
26
+ class_eval(<<-METH, __FILE__, __LINE__ + 1)
27
+ # lock.aware version of +#{lock_meth}+
28
+ def #{lock_meth}(*) # def close(*)
29
+ return super if @lock.owned?
30
+
31
+ # small race condition between
32
+ # checking for ownership and
33
+ # acquiring lock.
34
+ # TODO: fix this at the parser.
35
+ @lock.synchronize { super }
36
+ end
37
+ METH
38
+ end
39
+
40
+ private
41
+
42
+ %i[join_headers join_trailers join_body].each do |lock_meth|
43
+ class_eval(<<-METH, __FILE__, __LINE__ + 1)
44
+ # lock.aware version of +#{lock_meth}+
45
+ def #{lock_meth}(*) # def join_headers(*)
46
+ return super if @lock.owned?
47
+
48
+ # small race condition between
49
+ # checking for ownership and
50
+ # acquiring lock.
51
+ # TODO: fix this at the parser.
52
+ @lock.synchronize { super }
53
+ end
54
+ METH
55
+ end
56
+
57
+ def handle_stream(stream, request)
58
+ request.on(:body) do
59
+ next unless request.headers_sent
60
+
61
+ handle(request, stream)
62
+
63
+ emit(:flush_buffer)
64
+ end
65
+ super
66
+ end
67
+
68
+ # when there ain't more chunks, it makes the buffer as full.
69
+ def send_chunk(request, stream, chunk, next_chunk)
70
+ super
71
+
72
+ return if next_chunk
73
+
74
+ request.transition(:waiting_for_chunk)
75
+ throw(:buffer_full)
76
+ end
77
+
78
+ # sets end-stream flag when the request is closed.
79
+ def end_stream?(request, next_chunk)
80
+ request.closed? && next_chunk.nil?
81
+ end
82
+ end
83
+
84
+ # BidiBuffer is a Buffer which can be receive data from threads othr
85
+ # than the thread of the corresponding Connection/Session.
86
+ #
87
+ # It synchronizes access to a secondary internal +@oob_buffer+, which periodically
88
+ # is reconciled to the main internal +@buffer+.
89
+ class BidiBuffer < Buffer
90
+ def initialize(*)
91
+ super
92
+ @parent_thread = Thread.current
93
+ @oob_mutex = Thread::Mutex.new
94
+ @oob_buffer = "".b
95
+ end
96
+
97
+ # buffers the +chunk+ to be sent
98
+ def <<(chunk)
99
+ return super if Thread.current == @parent_thread
100
+
101
+ @oob_mutex.synchronize { @oob_buffer << chunk }
102
+ end
103
+
104
+ # reconciles the main and secondary buffer (which receives data from other threads).
105
+ def rebuffer
106
+ raise Error, "can only rebuffer while waiting on a response" unless Thread.current == @parent_thread
107
+
108
+ @oob_mutex.synchronize do
109
+ @buffer << @oob_buffer
110
+ @oob_buffer.clear
111
+ end
112
+ end
113
+ end
114
+
115
+ # Proxy to wake up the session main loop when one
116
+ # of the connections has buffered data to write. It abides by the HTTPX::_Selectable API,
117
+ # which allows it to be registered in the selector alongside actual HTTP-based
118
+ # HTTPX::Connection objects.
119
+ class Signal
120
+ def initialize
121
+ @closed = false
122
+ @pipe_read, @pipe_write = ::IO.pipe
123
+ end
124
+
125
+ def state
126
+ @closed ? :closed : :open
127
+ end
128
+
129
+ # noop
130
+ def log(**); end
131
+
132
+ def to_io
133
+ @pipe_read.to_io
134
+ end
135
+
136
+ def wakeup
137
+ return if @closed
138
+
139
+ @pipe_write.write("\0")
140
+ end
141
+
142
+ def call
143
+ return if @closed
144
+
145
+ @pipe_read.readpartial(1)
146
+ end
147
+
148
+ def interests
149
+ return if @closed
150
+
151
+ :r
152
+ end
153
+
154
+ def timeout; end
155
+
156
+ def terminate
157
+ @pipe_write.close
158
+ @pipe_read.close
159
+ @closed = true
160
+ end
161
+
162
+ # noop (the owner connection will take of it)
163
+ def handle_socket_timeout(interval); end
164
+ end
165
+
166
+ class << self
167
+ def load_dependencies(klass)
168
+ klass.plugin(:stream)
169
+ end
170
+
171
+ def extra_options(options)
172
+ options.merge(fallback_protocol: "h2")
173
+ end
174
+ end
175
+
176
+ module InstanceMethods
177
+ def initialize(*)
178
+ @signal = Signal.new
179
+ super
180
+ end
181
+
182
+ def close(selector = Selector.new)
183
+ @signal.terminate
184
+ selector.deregister(@signal)
185
+ super(selector)
186
+ end
187
+
188
+ def select_connection(connection, selector)
189
+ super
190
+ selector.register(@signal)
191
+ connection.signal = @signal
192
+ end
193
+
194
+ def deselect_connection(connection, *)
195
+ super
196
+ connection.signal = nil
197
+ end
198
+ end
199
+
200
+ # Adds synchronization to request operations which may buffer payloads from different
201
+ # threads.
202
+ module RequestMethods
203
+ attr_accessor :headers_sent
204
+
205
+ def initialize(*)
206
+ super
207
+ @headers_sent = false
208
+ @closed = false
209
+ @mutex = Thread::Mutex.new
210
+ end
211
+
212
+ def closed?
213
+ @closed
214
+ end
215
+
216
+ def can_buffer?
217
+ super && @state != :waiting_for_chunk
218
+ end
219
+
220
+ # overrides state management transitions to introduce an intermediate
221
+ # +:waiting_for_chunk+ state, which the request transitions to once payload
222
+ # is buffered.
223
+ def transition(nextstate)
224
+ headers_sent = @headers_sent
225
+
226
+ case nextstate
227
+ when :waiting_for_chunk
228
+ return unless @state == :body
229
+ when :body
230
+ case @state
231
+ when :headers
232
+ headers_sent = true
233
+ when :waiting_for_chunk
234
+ # HACK: to allow super to pass through
235
+ @state = :headers
236
+ end
237
+ end
238
+
239
+ super.tap do
240
+ # delay setting this up until after the first transition to :body
241
+ @headers_sent = headers_sent
242
+ end
243
+ end
244
+
245
+ def <<(chunk)
246
+ @mutex.synchronize do
247
+ if @drainer
248
+ @body.clear if @body.respond_to?(:clear)
249
+ @drainer = nil
250
+ end
251
+ @body << chunk
252
+
253
+ transition(:body)
254
+ end
255
+ end
256
+
257
+ def close
258
+ @mutex.synchronize do
259
+ return if @closed
260
+
261
+ @closed = true
262
+ end
263
+
264
+ # last chunk to send which ends the stream
265
+ self << ""
266
+ end
267
+ end
268
+
269
+ module RequestBodyMethods
270
+ def initialize(*, **)
271
+ super
272
+ @headers.delete("content-length")
273
+ end
274
+
275
+ def empty?
276
+ false
277
+ end
278
+ end
279
+
280
+ # overrides the declaration of +@write_buffer+, which is now a thread-safe buffer
281
+ # responding to the same API.
282
+ module ConnectionMethods
283
+ attr_writer :signal
284
+
285
+ def initialize(*)
286
+ super
287
+ @write_buffer = BidiBuffer.new(@options.buffer_size)
288
+ end
289
+
290
+ # rebuffers the +@write_buffer+ before calculating interests.
291
+ def interests
292
+ @write_buffer.rebuffer
293
+
294
+ super
295
+ end
296
+
297
+ private
298
+
299
+ def parser_type(protocol)
300
+ return HTTP2Bidi if protocol == "h2"
301
+
302
+ super
303
+ end
304
+
305
+ def set_parser_callbacks(parser)
306
+ super
307
+ parser.on(:flush_buffer) do
308
+ @signal.wakeup if @signal
309
+ end
310
+ end
311
+ end
312
+ end
313
+ register_plugin :stream_bidi, StreamBidi
314
+ end
315
+ end
data/lib/httpx/pool.rb CHANGED
@@ -13,25 +13,28 @@ module HTTPX
13
13
 
14
14
  # Sets up the connection pool with the given +options+, which can be the following:
15
15
  #
16
+ # :max_connections:: the maximum number of connections held in the pool.
16
17
  # :max_connections_per_origin :: the maximum number of connections held in the pool pointing to a given origin.
17
18
  # :pool_timeout :: the number of seconds to wait for a connection to a given origin (before raising HTTPX::PoolTimeoutError)
18
19
  #
19
20
  def initialize(options)
21
+ @max_connections = options.fetch(:max_connections, Float::INFINITY)
20
22
  @max_connections_per_origin = options.fetch(:max_connections_per_origin, Float::INFINITY)
21
23
  @pool_timeout = options.fetch(:pool_timeout, POOL_TIMEOUT)
22
24
  @resolvers = Hash.new { |hs, resolver_type| hs[resolver_type] = [] }
23
25
  @resolver_mtx = Thread::Mutex.new
24
26
  @connections = []
25
27
  @connection_mtx = Thread::Mutex.new
28
+ @connections_counter = 0
29
+ @max_connections_cond = ConditionVariable.new
26
30
  @origin_counters = Hash.new(0)
27
31
  @origin_conds = Hash.new { |hs, orig| hs[orig] = ConditionVariable.new }
28
32
  end
29
33
 
34
+ # connections returned by this function are not expected to return to the connection pool.
30
35
  def pop_connection
31
36
  @connection_mtx.synchronize do
32
- conn = @connections.shift
33
- @origin_conds.delete(conn.origin) if conn && (@origin_counters[conn.origin.to_s] -= 1).zero?
34
- conn
37
+ drop_connection
35
38
  end
36
39
  end
37
40
 
@@ -44,13 +47,34 @@ module HTTPX
44
47
 
45
48
  @connection_mtx.synchronize do
46
49
  acquire_connection(uri, options) || begin
50
+ if @connections_counter == @max_connections
51
+ # this takes precedence over per-origin
52
+ @max_connections_cond.wait(@connection_mtx, @pool_timeout)
53
+
54
+ acquire_connection(uri, options) || begin
55
+ if @connections_counter == @max_connections
56
+ # if no matching usable connection was found, the pool will make room and drop a closed connection. if none is found,
57
+ # this means that all of them are persistent or being used, so raise a timeout error.
58
+ conn = @connections.find { |c| c.state == :closed }
59
+
60
+ raise PoolTimeoutError.new(@pool_timeout,
61
+ "Timed out after #{@pool_timeout} seconds while waiting for a connection") unless conn
62
+
63
+ drop_connection(conn)
64
+ end
65
+ end
66
+ end
67
+
47
68
  if @origin_counters[uri.origin] == @max_connections_per_origin
48
69
 
49
70
  @origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
50
71
 
51
- return acquire_connection(uri, options) || raise(PoolTimeoutError.new(uri.origin, @pool_timeout))
72
+ return acquire_connection(uri, options) ||
73
+ raise(PoolTimeoutError.new(@pool_timeout,
74
+ "Timed out after #{@pool_timeout} seconds while waiting for a connection to #{uri.origin}"))
52
75
  end
53
76
 
77
+ @connections_counter += 1
54
78
  @origin_counters[uri.origin] += 1
55
79
 
56
80
  checkout_new_connection(uri, options)
@@ -64,6 +88,7 @@ module HTTPX
64
88
  @connection_mtx.synchronize do
65
89
  @connections << connection
66
90
 
91
+ @max_connections_cond.signal
67
92
  @origin_conds[connection.origin.to_s].signal
68
93
  end
69
94
  end
@@ -107,6 +132,15 @@ module HTTPX
107
132
  end
108
133
  end
109
134
 
135
+ # :nocov:
136
+ def inspect
137
+ "#<#{self.class}:#{object_id} " \
138
+ "@max_connections_per_origin=#{@max_connections_per_origin} " \
139
+ "@pool_timeout=#{@pool_timeout} " \
140
+ "@connections=#{@connections.size}>"
141
+ end
142
+ # :nocov:
143
+
110
144
  private
111
145
 
112
146
  def acquire_connection(uri, options)
@@ -114,7 +148,9 @@ module HTTPX
114
148
  connection.match?(uri, options)
115
149
  end
116
150
 
117
- @connections.delete_at(idx) if idx
151
+ return unless idx
152
+
153
+ @connections.delete_at(idx)
118
154
  end
119
155
 
120
156
  def checkout_new_connection(uri, options)
@@ -128,5 +164,22 @@ module HTTPX
128
164
  resolver_type.new(options)
129
165
  end
130
166
  end
167
+
168
+ # drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
169
+ # the first available connection from the pool will be dropped.
170
+ def drop_connection(connection = nil)
171
+ if connection
172
+ @connections.delete(connection)
173
+ else
174
+ connection = @connections.shift
175
+
176
+ return unless connection
177
+ end
178
+
179
+ @connections_counter -= 1
180
+ @origin_conds.delete(connection.origin) if (@origin_counters[connection.origin.to_s] -= 1).zero?
181
+
182
+ connection
183
+ end
131
184
  end
132
185
  end
@@ -116,7 +116,7 @@ module HTTPX
116
116
 
117
117
  # :nocov:
118
118
  def inspect
119
- "#<HTTPX::Request::Body:#{object_id} " \
119
+ "#<#{self.class}:#{object_id} " \
120
120
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
121
121
  end
122
122
  # :nocov:
data/lib/httpx/request.rb CHANGED
@@ -155,6 +155,10 @@ module HTTPX
155
155
  :w
156
156
  end
157
157
 
158
+ def can_buffer?
159
+ @state != :done
160
+ end
161
+
158
162
  # merges +h+ into the instance of HTTPX::Headers of the request.
159
163
  def merge_headers(h)
160
164
  @headers = @headers.merge(h)
@@ -222,7 +226,7 @@ module HTTPX
222
226
  return @query if defined?(@query)
223
227
 
224
228
  query = []
225
- if (q = @query_params)
229
+ if (q = @query_params) && !q.empty?
226
230
  query << Transcoder::Form.encode(q)
227
231
  end
228
232
  query << @uri.query if @uri.query
@@ -247,7 +251,7 @@ module HTTPX
247
251
 
248
252
  # :nocov:
249
253
  def inspect
250
- "#<HTTPX::Request:#{object_id} " \
254
+ "#<#{self.class}:#{object_id} " \
251
255
  "#{@verb} " \
252
256
  "#{uri} " \
253
257
  "@headers=#{@headers} " \
@@ -2,11 +2,14 @@
2
2
 
3
3
  require "resolv"
4
4
  require "uri"
5
- require "cgi"
6
5
  require "forwardable"
7
6
  require "httpx/base64"
8
7
 
9
8
  module HTTPX
9
+ # Implementation of a DoH name resolver (https://www.youtube.com/watch?v=unMXvnY2FNM).
10
+ # It wraps an HTTPX::Connection object which integrates with the main session in the
11
+ # same manner as other performed HTTP requests.
12
+ #
10
13
  class Resolver::HTTPS < Resolver::Resolver
11
14
  extend Forwardable
12
15
  using URIExtensions
@@ -27,14 +30,13 @@ module HTTPX
27
30
  use_get: false,
28
31
  }.freeze
29
32
 
30
- def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate, :inflight?
33
+ def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate, :inflight?, :handle_socket_timeout
31
34
 
32
35
  def initialize(_, options)
33
36
  super
34
37
  @resolver_options = DEFAULTS.merge(@options.resolver_options)
35
38
  @queries = {}
36
39
  @requests = {}
37
- @connections = []
38
40
  @uri = URI(@resolver_options[:uri])
39
41
  @uri_addresses = nil
40
42
  @resolver = Resolv::DNS.new
@@ -75,7 +77,11 @@ module HTTPX
75
77
 
76
78
  private
77
79
 
78
- def resolve(connection = @connections.first, hostname = nil)
80
+ def resolve(connection = nil, hostname = nil)
81
+ @connections.shift until @connections.empty? || @connections.first.state != :closed
82
+
83
+ connection ||= @connections.first
84
+
79
85
  return unless connection
80
86
 
81
87
  hostname ||= @queries.key(connection)
@@ -4,6 +4,9 @@ require "forwardable"
4
4
  require "resolv"
5
5
 
6
6
  module HTTPX
7
+ # Implements a pure ruby name resolver, which abides by the Selectable API.
8
+ # It delegates DNS payload encoding/decoding to the +resolv+ stlid gem.
9
+ #
7
10
  class Resolver::Native < Resolver::Resolver
8
11
  extend Forwardable
9
12
  using URIExtensions
@@ -34,7 +37,6 @@ module HTTPX
34
37
  @search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
35
38
  @_timeouts = Array(@resolver_options[:timeouts])
36
39
  @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
37
- @connections = []
38
40
  @name = nil
39
41
  @queries = {}
40
42
  @read_buffer = "".b
@@ -46,6 +48,10 @@ module HTTPX
46
48
  transition(:closed)
47
49
  end
48
50
 
51
+ def terminate
52
+ emit(:close, self)
53
+ end
54
+
49
55
  def closed?
50
56
  @state == :closed
51
57
  end
@@ -120,10 +126,7 @@ module HTTPX
120
126
  @ns_index += 1
121
127
  nameserver = @nameserver
122
128
  if nameserver && @ns_index < nameserver.size
123
- log do
124
- "resolver #{FAMILY_TYPES[@record_type]}: " \
125
- "failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})"
126
- end
129
+ log { "resolver #{FAMILY_TYPES[@record_type]}: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
127
130
  transition(:idle)
128
131
  @timeouts.clear
129
132
  retry
@@ -158,9 +161,7 @@ module HTTPX
158
161
  timeouts = @timeouts[h]
159
162
 
160
163
  if !timeouts.empty?
161
- log do
162
- "resolver #{FAMILY_TYPES[@record_type]}: timeout after #{interval}s, retry (with #{timeouts.first}s) #{h}..."
163
- end
164
+ log { "resolver #{FAMILY_TYPES[@record_type]}: timeout after #{interval}s, retry (with #{timeouts.first}s) #{h}..." }
164
165
  # must downgrade to tcp AND retry on same host as last
165
166
  downgrade_socket
166
167
  resolve(connection, h)
@@ -388,10 +389,9 @@ module HTTPX
388
389
 
389
390
  if hostname.nil?
390
391
  hostname = connection.peer.host
391
- log do
392
- "resolver #{FAMILY_TYPES[@record_type]}: " \
393
- "resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
394
- end if connection.peer.non_ascii_hostname
392
+ if connection.peer.non_ascii_hostname
393
+ log { "resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}" }
394
+ end
395
395
 
396
396
  hostname = generate_candidates(hostname).each do |name|
397
397
  @queries[name] = connection
@@ -507,7 +507,7 @@ module HTTPX
507
507
  end
508
508
 
509
509
  while (connection = @connections.shift)
510
- emit_resolve_error(connection, host, error)
510
+ emit_resolve_error(connection, connection.peer.host, error)
511
511
  end
512
512
  end
513
513
  end
@@ -4,6 +4,9 @@ require "resolv"
4
4
  require "ipaddr"
5
5
 
6
6
  module HTTPX
7
+ # Base class for all internal internet name resolvers. It handles basic blocks
8
+ # from the Selectable API.
9
+ #
7
10
  class Resolver::Resolver
8
11
  include Callbacks
9
12
  include Loggable
@@ -36,6 +39,7 @@ module HTTPX
36
39
  @family = family
37
40
  @record_type = RECORD_TYPES[family]
38
41
  @options = options
42
+ @connections = []
39
43
 
40
44
  set_resolver_callbacks
41
45
  end