httpx 1.5.1 → 1.6.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_0.md +50 -0
  3. data/lib/httpx/adapters/datadog.rb +23 -13
  4. data/lib/httpx/adapters/faraday.rb +14 -9
  5. data/lib/httpx/adapters/webmock.rb +1 -1
  6. data/lib/httpx/callbacks.rb +1 -1
  7. data/lib/httpx/connection/http1.rb +5 -6
  8. data/lib/httpx/connection/http2.rb +30 -12
  9. data/lib/httpx/connection.rb +17 -24
  10. data/lib/httpx/errors.rb +3 -1
  11. data/lib/httpx/io/ssl.rb +1 -4
  12. data/lib/httpx/io/tcp.rb +25 -16
  13. data/lib/httpx/io/unix.rb +4 -3
  14. data/lib/httpx/loggable.rb +4 -1
  15. data/lib/httpx/options.rb +252 -158
  16. data/lib/httpx/plugins/aws_sdk_authentication.rb +2 -0
  17. data/lib/httpx/plugins/aws_sigv4.rb +2 -0
  18. data/lib/httpx/plugins/callbacks.rb +13 -1
  19. data/lib/httpx/plugins/circuit_breaker.rb +2 -0
  20. data/lib/httpx/plugins/content_digest.rb +2 -0
  21. data/lib/httpx/plugins/cookies.rb +2 -2
  22. data/lib/httpx/plugins/digest_auth.rb +2 -0
  23. data/lib/httpx/plugins/expect.rb +2 -0
  24. data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
  25. data/lib/httpx/plugins/follow_redirects.rb +2 -0
  26. data/lib/httpx/plugins/grpc.rb +2 -0
  27. data/lib/httpx/plugins/h2c.rb +26 -16
  28. data/lib/httpx/plugins/internal_telemetry.rb +0 -49
  29. data/lib/httpx/plugins/ntlm_auth.rb +2 -0
  30. data/lib/httpx/plugins/oauth.rb +2 -0
  31. data/lib/httpx/plugins/persistent.rb +27 -18
  32. data/lib/httpx/plugins/proxy/socks4.rb +1 -1
  33. data/lib/httpx/plugins/proxy/socks5.rb +1 -1
  34. data/lib/httpx/plugins/proxy/ssh.rb +2 -0
  35. data/lib/httpx/plugins/proxy.rb +61 -20
  36. data/lib/httpx/plugins/response_cache/file_store.rb +2 -2
  37. data/lib/httpx/plugins/response_cache.rb +2 -0
  38. data/lib/httpx/plugins/retries.rb +2 -0
  39. data/lib/httpx/plugins/ssrf_filter.rb +2 -2
  40. data/lib/httpx/plugins/stream_bidi.rb +3 -3
  41. data/lib/httpx/plugins/upgrade/h2.rb +11 -1
  42. data/lib/httpx/plugins/upgrade.rb +8 -0
  43. data/lib/httpx/pool.rb +15 -10
  44. data/lib/httpx/request/body.rb +8 -3
  45. data/lib/httpx/request.rb +22 -11
  46. data/lib/httpx/resolver/entry.rb +30 -0
  47. data/lib/httpx/resolver/https.rb +3 -1
  48. data/lib/httpx/resolver/multi.rb +5 -2
  49. data/lib/httpx/resolver/native.rb +15 -6
  50. data/lib/httpx/resolver/resolver.rb +3 -5
  51. data/lib/httpx/resolver/system.rb +1 -1
  52. data/lib/httpx/resolver.rb +34 -21
  53. data/lib/httpx/response/body.rb +1 -1
  54. data/lib/httpx/response/buffer.rb +13 -18
  55. data/lib/httpx/selector.rb +92 -34
  56. data/lib/httpx/session.rb +89 -30
  57. data/lib/httpx/session_extensions.rb +3 -2
  58. data/lib/httpx/transcoder/form.rb +1 -13
  59. data/lib/httpx/transcoder/multipart/mime_type_detector.rb +1 -1
  60. data/lib/httpx/transcoder/multipart.rb +14 -0
  61. data/lib/httpx/transcoder/utils/deflater.rb +1 -1
  62. data/lib/httpx/version.rb +1 -1
  63. data/sig/callbacks.rbs +1 -1
  64. data/sig/chainable.rbs +1 -0
  65. data/sig/connection/http1.rbs +2 -0
  66. data/sig/connection/http2.rbs +4 -0
  67. data/sig/connection.rbs +6 -6
  68. data/sig/errors.rbs +3 -1
  69. data/sig/io/ssl.rbs +1 -1
  70. data/sig/io/tcp.rbs +13 -7
  71. data/sig/io/udp.rbs +7 -2
  72. data/sig/io/unix.rbs +0 -1
  73. data/sig/io.rbs +0 -3
  74. data/sig/options.rbs +63 -10
  75. data/sig/plugins/fiber_concurrency.rbs +51 -0
  76. data/sig/plugins/h2c.rbs +5 -1
  77. data/sig/plugins/persistent.rbs +1 -1
  78. data/sig/plugins/proxy/socks4.rbs +1 -1
  79. data/sig/plugins/proxy/socks5.rbs +1 -1
  80. data/sig/plugins/proxy.rbs +5 -2
  81. data/sig/plugins/ssrf_filter.rbs +1 -1
  82. data/sig/plugins/stream_bidi.rbs +2 -2
  83. data/sig/request.rbs +4 -1
  84. data/sig/resolver/entry.rbs +13 -0
  85. data/sig/resolver/native.rbs +1 -0
  86. data/sig/resolver/resolver.rbs +2 -3
  87. data/sig/resolver/system.rbs +2 -2
  88. data/sig/resolver.rbs +10 -11
  89. data/sig/response.rbs +2 -2
  90. data/sig/selector.rbs +18 -10
  91. data/sig/session.rbs +4 -0
  92. data/sig/transcoder/form.rbs +3 -3
  93. data/sig/transcoder/multipart.rbs +9 -3
  94. metadata +9 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9511c9d7e388161145434f6648b9d7d84b6144fd5b7cbdfd24bf4dcc1eae592
4
- data.tar.gz: 2e51144c54c1bf0d10d5c5df5587892b5b4672239d57417a608e3ae09463e22b
3
+ metadata.gz: 69e60280aa195822960c54a6e90e29a2dfc56cd400332fb39fa9747e14028595
4
+ data.tar.gz: 4395c88ede0b567c52c1cb67afcb11535cf4c0f797210b05b3771e884cb77d69
5
5
  SHA512:
6
- metadata.gz: fe4da43f48e9a7486a25388fb4bd2ee75400185e2ad509a3deed679b4f0fafc44a427054ede4d3a85844938289144913172a485d277250065b0e3df511a30e13
7
- data.tar.gz: b38686a69bededd7daba19ef0926ca52c1e02083c6c84e5b04845c6c866b83ed288516c98c2d08b6e675b1994ca73bb1806f630eb8880c9fc7bbfd4a57bba32a
6
+ metadata.gz: f0bdc947b9b401a5fdecd664ca857d75a36f4837d945cd7d4aff723e4ba2397c48faf5fb16b8a36265c8b82caaa772ad08776771ce48078000fc8e882ffe296a
7
+ data.tar.gz: 953d4930cdb8939af913fbdc16c034ffa2cd53e59c95bae61210e1b1a8e6afb82e3e0f83d590141acdecb1161e2cd067e9d0fde43bab696e222e3b955659a8b3
@@ -0,0 +1,50 @@
1
+ # 1.6.0
2
+
3
+ ## Features
4
+
5
+ ### `:fiber_concurrency` plugin
6
+
7
+ While simple use cases of `httpx` being used inside a fiber scheduler (such as [async](github.com/socketry/async)) worked, a serious of issues were identified when connections were reused across fibers for multiple requests (such as when using the `:persistent` plugin). The `:fiber_concurrency` plugin fixes that, by bookkeeping which requests are being used in which fibers in order to avoid spurious wakeups and busy loops.
8
+
9
+ This plugin is loaded by default when using the `:persistent` plugin, and by extension the `faraday` adapter.
10
+
11
+ You can read more about it in https://honeyryderchuck.gitlab.io/httpx/wiki/Fiber-Concurrency
12
+
13
+ ## Improvements
14
+
15
+ * proxy errors are now retriable (when using the `:retries` plugin alongside the `:proxy` plugin).
16
+ * several options improvements:
17
+ * improve initialization by caching options names in the class, which facilitates predictable option ivar initialization, which avoids "too many shapes" performance penalty.
18
+ * when using `HTTPX::Options#merge`, enforce usage of Hash or `HTTPX::Options` object as the argument to merge with, instead of silently ignoring when none of the former.
19
+ * `option_` setter methods are now private.
20
+ * all options ivars are frozen on initialize, which enforces the immutability guarantees (**note**: in case you were relying on it not being truly mutable, this may break your code. Try either passing immutable values to options, or instead use procs).
21
+ * several selector loop improvements:
22
+ * move selectable checks a layer above to avoid calling `IO.select` with a single socket at all costs.
23
+ * Improve connection interest calculation to reduce spurious wakeups.
24
+ * skip early when finding closed selectables
25
+ * improve connection initialization to avoid "too many shapes" performance penalty.
26
+ * Plugins are now able to extend the functionality of both HTTP1 and HTTP2 connections, as well as resolvers.
27
+ * see instructions in the [custom lugins wiki](https://gitlab.com/os85/httpx/-/wikis/Custom-Plugins).
28
+ * IP addresses which have expired their TTL (from the respective DNS answer) will be invalidated and force a new name resolution on operations requiring it, such as reconnections or cache lookups.
29
+ * connections are removed from errors which store it internally (for internal purposes) before returning the respective error response (so that it can be garbage collected).
30
+
31
+ ## Bugfixes
32
+
33
+ * remove check for non-unique local ipv6 which was preventing Happy Eyeballs v2 from kicking in.
34
+ * recover from "network unreachable" errors triggered by using a cached IP.
35
+ * dealing with requests which are rerouted for retries in the main session response handling loop.
36
+ * `datadog` adapter: compatibility with support versions under 1.13.0 is working again.
37
+ * http2 connection: fix calculation when connection closes and there's no termination handshake
38
+ * `callback_for`: check for existence of ivar `@callbacks` first.
39
+ * native resolver: do not buffer DNS query if waiting on a previous reply (even across fibers).
40
+ * http2: do not allow deactivating connections which finished all requests but are still waiting for the ack of the PING frame (which can happen in a multi-fiber usage scenario).
41
+ * pool: fix when connection is acquired after waiting on it; return it immediately, instead of bookkeeping on `:max_connections` (when defined).
42
+ * session: fix deactivation flow, when connections get deregistered from selector and removed from the array being iterated on.
43
+ * `:proxy` plugin: fix ssl reconnection such as, on close, the IO downgrades to the respective tcp socket, so that reconnection handshake starts from scratch with the original IO.
44
+ * `:persistent` plugin: calling `HTTPX::Session#close` will close selectors from other threads (instead of just the thread it was called on).
45
+ * `:callbacks` plugin: propagate callbacks to "derived-via `.plugin`" sessions.
46
+ * `:callbacks` plugin: do not trigger the `on_response_completed` callback if there was an error (as that's what the `on_request_error` callback is for).
47
+
48
+ ## Chore
49
+
50
+ * decoupled form and multipart transcoders, moved "form or multipart" check to request body.
@@ -13,7 +13,11 @@ module Datadog::Tracing
13
13
 
14
14
  TYPE_OUTBOUND = Datadog::Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
15
15
 
16
- TAG_BASE_SERVICE = Datadog::Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE
16
+ TAG_BASE_SERVICE = if Gem::Version.new(DATADOG_VERSION::STRING) < Gem::Version.new("1.15.0")
17
+ "_dd.base_service"
18
+ else
19
+ Datadog::Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE
20
+ end
17
21
  TAG_PEER_HOSTNAME = Datadog::Tracing::Metadata::Ext::TAG_PEER_HOSTNAME
18
22
 
19
23
  TAG_KIND = Datadog::Tracing::Metadata::Ext::TAG_KIND
@@ -64,16 +68,7 @@ module Datadog::Tracing
64
68
  end
65
69
 
66
70
  request.on(:response) do |response|
67
- unless span
68
- next unless response.is_a?(::HTTPX::ErrorResponse) && response.error.respond_to?(:connection)
69
-
70
- # handles the case when the +error+ happened during name resolution, which means
71
- # that the tracing start point hasn't been triggered yet; in such cases, the approximate
72
- # initial resolving time is collected from the connection, and used as span start time,
73
- # and the tracing object in inserted before the on response callback is called.
74
- span = initialize_span(request, response.error.connection.init_time)
75
-
76
- end
71
+ span = initialize_span(request, request.init_time) if !span && request.init_time
77
72
 
78
73
  finish(response, span)
79
74
  end
@@ -89,7 +84,7 @@ module Datadog::Tracing
89
84
 
90
85
  span.set_tags(
91
86
  Datadog.configuration.tracing.header_tags.response_tags(response.headers.to_h)
92
- )
87
+ ) if Datadog.configuration.tracing.respond_to?(:header_tags)
93
88
  end
94
89
 
95
90
  span.finish
@@ -139,7 +134,7 @@ module Datadog::Tracing
139
134
 
140
135
  span.set_tags(
141
136
  Datadog.configuration.tracing.header_tags.request_tags(request.headers.to_h)
142
- )
137
+ ) if Datadog.configuration.tracing.respond_to?(:header_tags)
143
138
 
144
139
  span
145
140
  rescue StandardError => e
@@ -185,14 +180,29 @@ module Datadog::Tracing
185
180
  end
186
181
 
187
182
  module RequestMethods
183
+ attr_reader :init_time
184
+
188
185
  # intercepts request initialization to inject the tracing logic.
189
186
  def initialize(*)
190
187
  super
191
188
 
189
+ @init_time = nil
190
+
192
191
  return unless Datadog::Tracing.enabled?
193
192
 
194
193
  RequestTracer.call(self)
195
194
  end
195
+
196
+ def response=(response)
197
+ if response.is_a?(::HTTPX::ErrorResponse) && response.error.respond_to?(:connection)
198
+ # handles the case when the +error+ happened during name resolution, which means
199
+ # that the tracing start point hasn't been triggered yet; in such cases, the approximate
200
+ # initial resolving time is collected from the connection, and used as span start time,
201
+ # and the tracing object in inserted before the on response callback is called.
202
+ @init_time = response.error.connection.init_time
203
+ end
204
+ super
205
+ end
196
206
  end
197
207
 
198
208
  module ConnectionMethods
@@ -7,9 +7,14 @@ require "faraday"
7
7
  module Faraday
8
8
  class Adapter
9
9
  class HTTPX < Faraday::Adapter
10
+ def initialize(app = nil, opts = {}, &block)
11
+ @connection = @bind = nil
12
+ super(app, opts, &block)
13
+ end
14
+
10
15
  module RequestMixin
11
16
  def build_connection(env)
12
- return @connection if defined?(@connection)
17
+ return @connection if @connection
13
18
 
14
19
  @connection = ::HTTPX.plugin(:persistent).plugin(ReasonPlugin)
15
20
  @connection = @connection.with(@connection_options) unless @connection_options.empty?
@@ -54,6 +59,8 @@ module Faraday
54
59
  Errno::EPIPE,
55
60
  ::HTTPX::ConnectionError => e
56
61
  raise Faraday::ConnectionFailed, e
62
+ rescue ::HTTPX::TimeoutError => e
63
+ raise Faraday::TimeoutError, e
57
64
  end
58
65
 
59
66
  def build_request(env)
@@ -125,9 +132,11 @@ module Faraday
125
132
  def response=(response)
126
133
  super
127
134
 
128
- return if response.is_a?(::HTTPX::ErrorResponse)
135
+ return unless @response
136
+
137
+ return if @response.is_a?(::HTTPX::ErrorResponse)
129
138
 
130
- response.body.on_data = @response_on_data
139
+ @response.body.on_data = @response_on_data
131
140
  end
132
141
  end
133
142
 
@@ -218,10 +227,10 @@ module Faraday
218
227
  handler.on_complete.call(handler.env) if handler.on_complete
219
228
  end
220
229
  end
221
- rescue ::HTTPX::TimeoutError => e
222
- raise Faraday::TimeoutError, e
223
230
  end
224
231
 
232
+ private
233
+
225
234
  # from Faraday::Adapter#connection
226
235
  def connection(env)
227
236
  conn = build_connection(env)
@@ -230,8 +239,6 @@ module Faraday
230
239
  yield conn
231
240
  end
232
241
 
233
- private
234
-
235
242
  # from Faraday::Adapter#request_timeout
236
243
  def request_timeout(type, options)
237
244
  key = Faraday::Adapter::TIMEOUT_KEYS[type]
@@ -284,8 +291,6 @@ module Faraday
284
291
  response.raise_for_status unless response.is_a?(::HTTPX::Response)
285
292
  response
286
293
  end
287
- rescue ::HTTPX::TimeoutError => e
288
- raise Faraday::TimeoutError, e
289
294
  end
290
295
 
291
296
  def parallel?(env)
@@ -60,7 +60,7 @@ module WebMock
60
60
  connection.once(:unmock_connection) do
61
61
  next unless connection.current_session == self
62
62
 
63
- unless connection.addresses
63
+ unless connection.addresses?
64
64
  # reset Happy Eyeballs, fail early
65
65
  connection.sibling = nil
66
66
 
@@ -20,7 +20,7 @@ module HTTPX
20
20
  end
21
21
 
22
22
  def callbacks_for?(type)
23
- @callbacks.key?(type) && @callbacks[type].any?
23
+ @callbacks && @callbacks.key?(type) && @callbacks[type].any?
24
24
  end
25
25
 
26
26
  protected
@@ -31,12 +31,7 @@ module HTTPX
31
31
  end
32
32
 
33
33
  def interests
34
- # this means we're processing incoming response already
35
- return :r if @request
36
-
37
- return if @requests.empty?
38
-
39
- request = @requests.first
34
+ request = @request || @requests.first
40
35
 
41
36
  return unless request
42
37
 
@@ -229,6 +224,10 @@ module HTTPX
229
224
  emit(:exhausted)
230
225
  end
231
226
 
227
+ def waiting_for_ping?
228
+ false
229
+ end
230
+
232
231
  private
233
232
 
234
233
  def manage_connection(request, response)
@@ -35,7 +35,7 @@ module HTTPX
35
35
  @settings = @options.http2_settings
36
36
  @pending = []
37
37
  @streams = {}
38
- @drains = {}
38
+ @drains = {}
39
39
  @pings = []
40
40
  @buffer = buffer
41
41
  @handshake_completed = false
@@ -52,12 +52,11 @@ module HTTPX
52
52
  end
53
53
 
54
54
  def interests
55
- # waiting for WINDOW_UPDATE frames
56
- return :r if @buffer.full?
57
-
58
55
  if @connection.state == :closed
59
56
  return unless @handshake_completed
60
57
 
58
+ return if @buffer.empty?
59
+
61
60
  return :w
62
61
  end
63
62
 
@@ -65,6 +64,13 @@ module HTTPX
65
64
  return @buffer.empty? ? :r : :rw
66
65
  end
67
66
 
67
+ unless @connection.send_buffer.empty?
68
+ return :rw unless @buffer.empty?
69
+
70
+ # waiting for WINDOW_UPDATE frames
71
+ return :r
72
+ end
73
+
68
74
  return :w if !@pending.empty? && can_buffer_more_requests?
69
75
 
70
76
  return :w unless @drains.empty?
@@ -72,10 +78,10 @@ module HTTPX
72
78
  if @buffer.empty?
73
79
  return if @streams.empty? && @pings.empty?
74
80
 
75
- return :r
81
+ :r
82
+ else
83
+ :w
76
84
  end
77
-
78
- :rw
79
85
  end
80
86
 
81
87
  def close
@@ -151,6 +157,10 @@ module HTTPX
151
157
  @pings << ping
152
158
  end
153
159
 
160
+ def waiting_for_ping?
161
+ @pings.any?
162
+ end
163
+
154
164
  private
155
165
 
156
166
  def can_buffer_more_requests?
@@ -229,7 +239,7 @@ module HTTPX
229
239
  end
230
240
 
231
241
  log(level: 1, color: :yellow) do
232
- request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact(v)}" }.join("\n")
242
+ "\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact(v)}" }.join("\n")}"
233
243
  end
234
244
  stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
235
245
  end
@@ -323,8 +333,7 @@ module HTTPX
323
333
  return if error == :stream_closed && !@streams.key?(request)
324
334
 
325
335
  log(level: 2) { "#{stream.id}: closing stream" }
326
- @drains.delete(request)
327
- @streams.delete(request)
336
+ teardown(request)
328
337
 
329
338
  if error
330
339
  case error
@@ -379,8 +388,7 @@ module HTTPX
379
388
  when :no_error
380
389
  ex = GoawayError.new
381
390
  @pending.unshift(*@streams.keys)
382
- @drains.clear
383
- @streams.clear
391
+ teardown
384
392
  else
385
393
  ex = Error.new(0, error)
386
394
  end
@@ -448,5 +456,15 @@ module HTTPX
448
456
 
449
457
  emit(:pong)
450
458
  end
459
+
460
+ def teardown(request = nil)
461
+ if request
462
+ @drains.delete(request)
463
+ @streams.delete(request)
464
+ else
465
+ @drains.clear
466
+ @streams.clear
467
+ end
468
+ end
451
469
  end
452
470
  end
@@ -34,9 +34,6 @@ module HTTPX
34
34
 
35
35
  using URIExtensions
36
36
 
37
- require "httpx/connection/http2"
38
- require "httpx/connection/http1"
39
-
40
37
  def_delegator :@io, :closed?
41
38
 
42
39
  def_delegator :@write_buffer, :empty?
@@ -50,7 +47,11 @@ module HTTPX
50
47
  protected :sibling
51
48
 
52
49
  def initialize(uri, options)
53
- @current_session = @current_selector = @sibling = @coalesced_connection = nil
50
+ @current_session = @current_selector =
51
+ @parser = @sibling = @coalesced_connection =
52
+ @family = @io = @ssl_session = @timeout =
53
+ @connected_at = @response_received_at = nil
54
+
54
55
  @exhausted = @cloned = @main_sibling = false
55
56
 
56
57
  @options = Options.new(options)
@@ -61,6 +62,8 @@ module HTTPX
61
62
  @read_buffer = Buffer.new(@options.buffer_size)
62
63
  @write_buffer = Buffer.new(@options.buffer_size)
63
64
  @pending = []
65
+ @inflight = 0
66
+ @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
64
67
 
65
68
  on(:error, &method(:on_error))
66
69
  if @options.io
@@ -98,9 +101,6 @@ module HTTPX
98
101
  build_altsvc_connection(alt_origin, origin, alt_params)
99
102
  end
100
103
 
101
- @inflight = 0
102
- @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
103
-
104
104
  self.addresses = @options.addresses if @options.addresses
105
105
  end
106
106
 
@@ -122,6 +122,10 @@ module HTTPX
122
122
  @io && @io.addresses
123
123
  end
124
124
 
125
+ def addresses?
126
+ @io && @io.addresses?
127
+ end
128
+
125
129
  def match?(uri, options)
126
130
  return false if !used? && (@state == :closing || @state == :closed)
127
131
 
@@ -135,12 +139,6 @@ module HTTPX
135
139
  ) && @options == options
136
140
  end
137
141
 
138
- def expired?
139
- return false unless @io
140
-
141
- @io.expired?
142
- end
143
-
144
142
  def mergeable?(connection)
145
143
  return false if @state == :closing || @state == :closed || !@io
146
144
 
@@ -229,9 +227,6 @@ module HTTPX
229
227
  return @io.interests if connecting?
230
228
  end
231
229
 
232
- # if the write buffer is full, we drain it
233
- return :w unless @write_buffer.empty?
234
-
235
230
  return @parser.interests if @parser
236
231
 
237
232
  nil
@@ -369,8 +364,6 @@ module HTTPX
369
364
  end
370
365
 
371
366
  def handle_connect_error(error)
372
- @connect_error = error
373
-
374
367
  return handle_error(error) unless @sibling && @sibling.connecting?
375
368
 
376
369
  @sibling.merge(self)
@@ -382,8 +375,7 @@ module HTTPX
382
375
  return unless @current_session && @current_selector
383
376
 
384
377
  emit(:close)
385
- @current_session = nil
386
- @current_selector = nil
378
+ @current_session = @current_selector = nil
387
379
  end
388
380
 
389
381
  # :nocov:
@@ -545,7 +537,7 @@ module HTTPX
545
537
 
546
538
  def send_request_to_parser(request)
547
539
  @inflight += 1
548
- request.peer_address = @io.ip
540
+ request.peer_address = @io.ip && @io.ip.address
549
541
  set_request_timeouts(request)
550
542
 
551
543
  parser.send(request)
@@ -714,7 +706,7 @@ module HTTPX
714
706
  return unless @state == :open
715
707
 
716
708
  # do not deactivate connection in use
717
- return if @inflight.positive?
709
+ return if @inflight.positive? || @parser.waiting_for_ping?
718
710
  when :closing
719
711
  return unless @state == :idle || @state == :open
720
712
 
@@ -748,6 +740,7 @@ module HTTPX
748
740
  # activate
749
741
  @current_session.select_connection(self, @current_selector)
750
742
  end
743
+ log(level: 3) { "#{@state} -> #{nextstate}" }
751
744
  @state = nextstate
752
745
  end
753
746
 
@@ -947,8 +940,8 @@ module HTTPX
947
940
 
948
941
  def parser_type(protocol)
949
942
  case protocol
950
- when "h2" then HTTP2
951
- when "http/1.1" then HTTP1
943
+ when "h2" then @options.http2_class
944
+ when "http/1.1" then @options.http1_class
952
945
  else
953
946
  raise Error, "unsupported protocol (##{protocol})"
954
947
  end
data/lib/httpx/errors.rb CHANGED
@@ -77,7 +77,9 @@ module HTTPX
77
77
  # Error raised when there was an error while resolving a domain to an IP
78
78
  # using a HTTPX::Resolver::Native resolver.
79
79
  class NativeResolveError < ResolveError
80
- attr_reader :connection, :host
80
+ attr_reader :host
81
+
82
+ attr_accessor :connection
81
83
 
82
84
  # initializes the exception with the +connection+ it refers to, the +host+ domain
83
85
  # which failed to resolve, and the error +message+.
data/lib/httpx/io/ssl.rb CHANGED
@@ -19,6 +19,7 @@ module HTTPX
19
19
  def initialize(_, _, options)
20
20
  super
21
21
 
22
+ @ssl_session = nil
22
23
  ctx_options = TLS_OPTIONS.merge(options.ssl)
23
24
  @sni_hostname = ctx_options.delete(:hostname) || @hostname
24
25
 
@@ -84,10 +85,6 @@ module HTTPX
84
85
  @state == :negotiated
85
86
  end
86
87
 
87
- def expired?
88
- super || ssl_session_expired?
89
- end
90
-
91
88
  def ssl_session_expired?
92
89
  @ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
93
90
  end
data/lib/httpx/io/tcp.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "resolv"
4
- require "ipaddr"
5
4
 
6
5
  module HTTPX
7
6
  class TCP
@@ -30,7 +29,8 @@ module HTTPX
30
29
  end
31
30
  raise Error, "Given IO objects do not match the request authority" unless @io
32
31
 
33
- _, _, _, @ip = @io.addr
32
+ _, _, _, ip = @io.addr
33
+ @ip = Resolver::Entry.new(ip)
34
34
  @addresses << @ip
35
35
  @keep_open = true
36
36
  @state = :connected
@@ -47,8 +47,6 @@ module HTTPX
47
47
  def add_addresses(addrs)
48
48
  return if addrs.empty?
49
49
 
50
- addrs = addrs.map { |addr| addr.is_a?(IPAddr) ? addr : IPAddr.new(addr) }
51
-
52
50
  ip_index = @ip_index || (@addresses.size - 1)
53
51
  if addrs.first.ipv6?
54
52
  # should be the next in line
@@ -59,6 +57,19 @@ module HTTPX
59
57
  end
60
58
  end
61
59
 
60
+ # eliminates expired entries and returns whether there are still any left.
61
+ def addresses?
62
+ prev_addr_size = @addresses.size
63
+
64
+ @addresses.delete_if(&:expired?)
65
+
66
+ unless (decr = prev_addr_size - @addresses.size).zero?
67
+ @ip_index = @addresses.size - decr
68
+ end
69
+
70
+ @addresses.any?
71
+ end
72
+
62
73
  def to_io
63
74
  @io.to_io
64
75
  end
@@ -75,9 +86,18 @@ module HTTPX
75
86
  @io = build_socket
76
87
  end
77
88
  try_connect
89
+ rescue Errno::EHOSTUNREACH,
90
+ Errno::ENETUNREACH => e
91
+ raise e if @ip_index <= 0
92
+
93
+ log { "failed connecting to #{@ip} (#{e.message}), evict from cache and trying next..." }
94
+ Resolver.cached_lookup_evict(@hostname, @ip)
95
+
96
+ @ip_index -= 1
97
+ @io = build_socket
98
+ retry
78
99
  rescue Errno::ECONNREFUSED,
79
100
  Errno::EADDRNOTAVAIL,
80
- Errno::EHOSTUNREACH,
81
101
  SocketError,
82
102
  IOError => e
83
103
  raise e if @ip_index <= 0
@@ -154,17 +174,6 @@ module HTTPX
154
174
  @state == :idle || @state == :closed
155
175
  end
156
176
 
157
- def expired?
158
- # do not mess with external sockets
159
- return false if @options.io
160
-
161
- return true unless @addresses
162
-
163
- resolver_addresses = Resolver.nolookup_resolve(@hostname)
164
-
165
- (Array(resolver_addresses) & @addresses).empty?
166
- end
167
-
168
177
  # :nocov:
169
178
  def inspect
170
179
  "#<#{self.class}:#{object_id} " \
data/lib/httpx/io/unix.rb CHANGED
@@ -48,11 +48,12 @@ module HTTPX
48
48
  transition(:connected)
49
49
  rescue Errno::EINPROGRESS,
50
50
  Errno::EALREADY,
51
- ::IO::WaitReadable
51
+ IO::WaitReadable
52
52
  end
53
53
 
54
- def expired?
55
- false
54
+ # the path is always explicitly passed, so no point in resolving.
55
+ def addresses?
56
+ true
56
57
  end
57
58
 
58
59
  # :nocov:
@@ -34,7 +34,10 @@ module HTTPX
34
34
  klass = klass.superclass
35
35
  end
36
36
 
37
- message = +"(pid:#{Process.pid} tid:#{Thread.current.object_id}, self:#{class_name}##{object_id}) "
37
+ message = +"(pid:#{Process.pid}, " \
38
+ "tid:#{Thread.current.object_id}, " \
39
+ "fid:#{Fiber.current.object_id}, " \
40
+ "self:#{class_name}##{object_id}) "
38
41
  message << msg.call << "\n"
39
42
  message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
40
43
  debug_stream << message