httpx 1.7.5 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e9530bb05303e7e15a05a84a26650fc2dd0ec0c35c11c909b07d8deb499f00f
4
- data.tar.gz: 29043b781e3f12cf18ca80eeb6979833536b659dd13ce76d5312fe20f3b61342
3
+ metadata.gz: b34abe93b42862837d9131524d6e9663a93474f70c65ac2a10946eb62c9aeb14
4
+ data.tar.gz: '0853b845878005ace796106f79b735d9d75e205b7ab28c03a3786b53233d6a4b'
5
5
  SHA512:
6
- metadata.gz: dea67bc736ed82b32c6c81257d66ca2dac2fae2cf41cb923fdca28c4c9741e5f57b0b56b3bfa23f6b0990f6faf26d14258b296ef771681d405564e02fafaceb5
7
- data.tar.gz: 13dda593ac028e1d34b6ca0e22dd6a024705bf542da756a013f5da7f0abcccaab76860e80e5d8586ff95d4fe258542a295c004d33866a7986407c6cd4950f9e9
6
+ metadata.gz: 0be425ba5468990b31fb3fe04b97ee8c727cc1f16690decc2057314c3645e5baa21fae9b54585bc6e628410374dbfd9b8676df3aaed180b321a9b59cbbad2853
7
+ data.tar.gz: 3cbe9b0e2952c7d330bffe727f2b478f53152d431a306d48f5f41cb5033e9ba7fa2ddec9a319aed49f051c8e9e6e2d8e489ecccc83a6c622246ef6922c9bbecc
@@ -17,7 +17,7 @@ You can pass chain several tracers, and callbacks will be relayed to all of them
17
17
  HTTP.plugin(:tracing).with(tracer: telemetry_platform_tracer).with(tracer: telemetry2_platform_tracer)
18
18
  ```
19
19
 
20
- This was developed to be the foundation on top of which the datadag and OTel integrations will be built.
20
+ This was developed to be the foundation on top of which the datadog and OTel integrations will be built.
21
21
 
22
22
  ## Improvements
23
23
 
@@ -0,0 +1,24 @@
1
+ # 1.7.6
2
+
3
+ ## Improvements
4
+
5
+ * `datadog` adapter: support setting custom `peer.service`.
6
+ * stopped doing `Thread.pass` after checking connections back into the pool. The goal of this was to allow threads potentially waiting on a connection for the same origin to have the opportunity to take it before the same thread could check the same connection out, thereby preventing starvation on limited pool sizes; this however backfired for the common case of unbounded pool sizes (the default), and was specially bad when used for multiple concurrent requests on multiple origins, where avoidable lag was introduced on the whole processing every time a connection was checked back in. Thereby, the initial problem needs to be solved at the mutex implementation of CRuby.
7
+ * connection: after keep alive timeout expired, try writing `PING` frame to the socket as soon as it's emitted (previously, the frame was being buffered and the socket would be probed for readiness before writing it, whereas in most cases this is avoidable).
8
+
9
+
10
+ ## Bugfixes
11
+
12
+ * reuse the same resolver instance when resolving multiple concurrent requests in the same option (it was previously instantiating different ones based on option heuristics, such as when requests had different `:origin`).
13
+ * on successful early name resolution (from cache or hosts file, for example), check the resolver back to the pool (the object was being dereferenced).
14
+ * http1 parser: turn buffer back into a string on reset, instead of preserving its current type (in cases where it was a chunked transfer decoder and connection was kept-alive, it was keeping it around and using it to parse the next request, which broke).
15
+ * http1 parser: enable pipelining based on the max concurrent requests, which the connection may have had pre-set to 1 based on prior knowledge of origin or other heuristics.
16
+ * http1 parser: when disabling pipelining, mark incomplete inlined requests as idle before sending them back to the pending queue.
17
+ * http1 and http2 parser: remove references to requests escalating an error; in scenarios of connection reuse (such as when using the `:persistent` plugin), these requests could be, on a transition to idle state, resent even in the absence of an external session reference, causing avoidable overhead or contention leading to timeouts or errors.
18
+ * connection: rescue and ignore exceptions when calling `OpenSSL::SSL::SSLSocket#close` or `Socket#close`.
19
+ * connection: when sending multiple requests into an open HTTP/2 connection which keep alive may have expired, only a single (instead of multiple, as before) `PING` frame will be sent (in the prior state, only the first `PING` was being acknowledged, which made the connection accumulate unacknowledged ping payloads).
20
+ * connection: on handling errors, preventing potential race condition by copying pending requests into a local var before calling `parser.handle_error` (this may trigger a callback which calls `#reset`, which may call `#disconnect`, which would check the connection back into the pool and make it available for another thread, which could change the state of the ivar containing pending requests).
21
+ * connection: skip resetting a connection when idle and having pending requests; this may happen in situation where parsers may have reset-and-back-to-idle the connection to resend failed requests as protocol (such as in the case of failed HTTP/1 pipelinining requests where incomplete requests are to be resent to the connection once pipelining is disabled).
22
+ * connection: raise request timeout errors for the request the connection is currently in, instead of the connection where it was initially sent on and set timeouts on; this happens in situations where the request may be retried and sent to a different connection, which forces the request to keep a reference to the connection it's currently in (and discard it when it no longer needs it to prevent it from being GC'ed).
23
+ * `:auth` plugin: when used alongside the `:retries` plugin for multiple concurrent requests, it was calling the dynamic token generator block once for each request; fixed to call the block only once per retry window, so retrying requests will reuse the same.
24
+ * `:retries` plugin: retry request immediately if `:retry_after` is negative (while the option is validated for numbers, it can't control proc-based calculation).
@@ -19,6 +19,7 @@ module Datadog::Tracing
19
19
  Datadog::Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE
20
20
  end
21
21
  TAG_PEER_HOSTNAME = Datadog::Tracing::Metadata::Ext::TAG_PEER_HOSTNAME
22
+ TAG_PEER_SERVICE = Datadog::Tracing::Metadata::Ext::TAG_PEER_SERVICE
22
23
 
23
24
  TAG_KIND = Datadog::Tracing::Metadata::Ext::TAG_KIND
24
25
  TAG_CLIENT = Datadog::Tracing::Metadata::Ext::SpanKind::TAG_CLIENT
@@ -108,7 +109,9 @@ module Datadog::Tracing
108
109
  span.set_tag(TAG_PEER_HOSTNAME, uri.host)
109
110
 
110
111
  # Tag as an external peer service
111
- # span.set_tag(TAG_PEER_SERVICE, span.service)
112
+ if (peer_service = config[:peer_service])
113
+ span.set_tag(TAG_PEER_SERVICE, peer_service)
114
+ end
112
115
 
113
116
  if config[:distributed_tracing]
114
117
  propagate_trace_http(
@@ -210,6 +213,11 @@ module Datadog::Tracing
210
213
  o.env "DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE"
211
214
  o.default 1.0
212
215
  end
216
+
217
+ option :peer_service do |o|
218
+ o.type :string, nilable: true
219
+ o.env "DD_TRACE_HTTPX_PEER_SERVICE"
220
+ end
213
221
  else
214
222
  option :enabled do |o|
215
223
  o.default { env_to_bool("DD_TRACE_HTTPX_ENABLED", true) }
@@ -225,6 +233,11 @@ module Datadog::Tracing
225
233
  o.default { env_to_float(%w[DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE DD_HTTPX_ANALYTICS_SAMPLE_RATE], 1.0) }
226
234
  o.lazy
227
235
  end
236
+
237
+ option :peer_service do |o|
238
+ o.default { env_to_string("DD_TRACE_HTTPX_PEER_SERVICE", nil) }
239
+ o.lazy
240
+ end
228
241
  end
229
242
 
230
243
  if defined?(Datadog::Tracing::Contrib::SpanAttributeSchema)
data/lib/httpx/altsvc.rb CHANGED
@@ -10,6 +10,8 @@ module HTTPX
10
10
 
11
11
  H2_ALTSVC_SCHEMES = %w[https h2].freeze
12
12
 
13
+ ALTSVC_IGNORE_IVARS = %i[@ssl].freeze
14
+
13
15
  def send(request)
14
16
  request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
15
17
 
@@ -35,11 +37,11 @@ module HTTPX
35
37
  end
36
38
 
37
39
  def match_altsvc_options?(uri, options)
38
- return @options == options unless @options.ssl.all? do |k, v|
40
+ return @options.connection_options_match?(options) unless @options.ssl.all? do |k, v|
39
41
  v == (k == :hostname ? uri.host : options.ssl[k])
40
42
  end
41
43
 
42
- @options.options_equals?(options, Options::REQUEST_BODY_IVARS + %i[@ssl])
44
+ @options.connection_options_match?(options, ALTSVC_IGNORE_IVARS)
43
45
  end
44
46
 
45
47
  def altsvc_match?(uri, other_uri)
@@ -28,7 +28,8 @@ module HTTPX
28
28
  @version = [1, 1]
29
29
  @pending = []
30
30
  @requests = []
31
- @handshake_completed = false
31
+ @request = nil
32
+ @handshake_completed = @pipelining = false
32
33
  end
33
34
 
34
35
  def timeout
@@ -53,7 +54,9 @@ module HTTPX
53
54
  end
54
55
 
55
56
  def reset_requests
56
- @pending.unshift(*@requests)
57
+ requests = @requests
58
+ requests.each { |r| r.transition(:idle) }
59
+ @pending.unshift(*requests)
57
60
  @requests.clear
58
61
  end
59
62
 
@@ -90,7 +93,7 @@ module HTTPX
90
93
  return if @requests.include?(request)
91
94
 
92
95
  @requests << request
93
- @pipelining = true if @requests.size > 1
96
+ @pipelining = @max_concurrent_requests > 1 && @requests.size > 1
94
97
  end
95
98
 
96
99
  def consume
@@ -145,15 +148,17 @@ module HTTPX
145
148
 
146
149
  return unless request
147
150
 
148
- log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
149
- log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
150
- response = request.response
151
+ begin
152
+ log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
153
+ log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
154
+ response = request.response
151
155
 
152
- response << chunk
153
- rescue StandardError => e
154
- error_response = ErrorResponse.new(request, e)
155
- request.response = error_response
156
- dispatch
156
+ response << chunk
157
+ rescue StandardError => e
158
+ error_response = ErrorResponse.new(request, e)
159
+ request.response = error_response
160
+ dispatch(request)
161
+ end
157
162
  end
158
163
 
159
164
  def on_complete
@@ -162,12 +167,10 @@ module HTTPX
162
167
  return unless request
163
168
 
164
169
  log(level: 2) { "parsing complete" }
165
- dispatch
170
+ dispatch(request)
166
171
  end
167
172
 
168
- def dispatch
169
- request = @request
170
-
173
+ def dispatch(request)
171
174
  if request.expects?
172
175
  @parser.reset!
173
176
  return handle(request)
@@ -217,12 +220,12 @@ module HTTPX
217
220
  if @pipelining
218
221
  catch(:called) { disable }
219
222
  else
220
- @requests.each do |req|
223
+ while (req = @requests.shift)
221
224
  next if request && request == req
222
225
 
223
226
  emit(:error, req, ex)
224
227
  end
225
- @pending.each do |req|
228
+ while (req = @pending.shift)
226
229
  next if request && request == req
227
230
 
228
231
  emit(:error, req, ex)
@@ -57,29 +57,37 @@ module HTTPX
57
57
 
58
58
  return if @buffer.empty?
59
59
 
60
+ # HTTP/2 GOAWAY frame buffered.
60
61
  return :w
61
62
  end
62
63
 
63
64
  unless @connection.state == :connected && @handshake_completed
65
+ # HTTP/2 in intermediate state or still completing initialization-
64
66
  return @buffer.empty? ? :r : :rw
65
67
  end
66
68
 
67
69
  unless @connection.send_buffer.empty?
70
+ # HTTP/2 connection is buffering data chunks and failing to emit DATA frames,
71
+ # most likely because the flow control window is exhausted.
68
72
  return :rw unless @buffer.empty?
69
73
 
70
74
  # waiting for WINDOW_UPDATE frames
71
75
  return :r
72
76
  end
73
77
 
78
+ # there are pending bufferable requests
74
79
  return :w if !@pending.empty? && can_buffer_more_requests?
75
80
 
81
+ # there are pending frames from the last run
76
82
  return :w unless @drains.empty?
77
83
 
78
84
  if @buffer.empty?
85
+ # skip if no more requests or pings to process
79
86
  return if @streams.empty? && @pings.empty?
80
87
 
81
88
  :r
82
89
  else
90
+ # buffered frames
83
91
  :w
84
92
  end
85
93
  end
@@ -138,7 +146,7 @@ module HTTPX
138
146
  settings_ex.set_backtrace(ex.backtrace)
139
147
  ex = settings_ex
140
148
  end
141
- @streams.each_key do |req|
149
+ while (req, _ = @streams.shift)
142
150
  next if request && request == req
143
151
 
144
152
  emit(:error, req, ex)
@@ -399,10 +407,9 @@ module HTTPX
399
407
  ex = GoawayError.new(error)
400
408
  ex.set_backtrace(caller)
401
409
 
402
- @pending.unshift(*@streams.keys)
410
+ handle_error(ex)
403
411
  teardown
404
412
 
405
- handle_error(ex)
406
413
  end
407
414
  end
408
415
  return unless is_connection_closed && @streams.empty?
@@ -62,6 +62,7 @@ module HTTPX
62
62
  @pending = []
63
63
  @inflight = 0
64
64
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
65
+ @no_more_requests_counter = 0
65
66
 
66
67
  if @options.io
67
68
  # if there's an already open IO, get its
@@ -106,7 +107,7 @@ module HTTPX
106
107
  # origin came from an ORIGIN frame, we're going to verify the hostname with the
107
108
  # SSL certificate
108
109
  (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) &&
109
- @options == options
110
+ @options.connection_options_match?(options)
110
111
  end
111
112
 
112
113
  def mergeable?(connection)
@@ -117,7 +118,7 @@ module HTTPX
117
118
  (
118
119
  (open? && @origin == connection.origin) ||
119
120
  !(@io.addresses & (connection.addresses || [])).empty?
120
- ) && @options == connection.options
121
+ ) && @options.connection_options_match?(connection.options)
121
122
  end
122
123
 
123
124
  # coalesces +self+ into +connection+.
@@ -287,6 +288,10 @@ module HTTPX
287
288
  def reset
288
289
  return if @state == :closing || @state == :closed
289
290
 
291
+ # do not reset a connection which may have restarted back to :idle, such when the parser resets
292
+ # (example: HTTP/1 parser disabling pipelining)
293
+ return if @state == :idle && @pending.any?
294
+
290
295
  parser = @parser
291
296
 
292
297
  if parser && parser.respond_to?(:max_concurrent_requests)
@@ -313,8 +318,8 @@ module HTTPX
313
318
  log(level: 3) { "keep alive timeout expired, pinging connection..." }
314
319
  @pending << request
315
320
  transition(:active) if @state == :inactive
316
- parser.ping
317
321
  request.ping!
322
+ ping
318
323
  return
319
324
  end
320
325
 
@@ -455,11 +460,11 @@ module HTTPX
455
460
  #
456
461
  # this condition takes into account:
457
462
  #
458
- # * the number of inflight requests
459
463
  # * the number of pending requests
464
+ # * the number of inflight requests
460
465
  # * whether the write buffer has bytes (i.e. for close handshake)
461
466
  if @pending.empty? && @inflight.zero? && @write_buffer.empty?
462
- log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
467
+ no_more_requests_loop_check if @parser && @parser.pending.any?
463
468
 
464
469
  # terminate if an altsvc connection has been established
465
470
  terminate if @altsvc_connection
@@ -509,7 +514,7 @@ module HTTPX
509
514
 
510
515
  # exit #consume altogether if all outstanding requests have been dealt with
511
516
  if @pending.empty? && @inflight.zero? && @write_buffer.empty? # rubocop:disable Style/Next
512
- log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
517
+ no_more_requests_loop_check if @parser && @parser.pending.any?
513
518
 
514
519
  # terminate if an altsvc connection has been established
515
520
  terminate if @altsvc_connection
@@ -635,6 +640,7 @@ module HTTPX
635
640
  build_altsvc_connection(alt_origin, origin, alt_params)
636
641
  end
637
642
  @response_received_at = Utils.now
643
+ @no_more_requests_counter = 0
638
644
  @inflight -= 1
639
645
  response.finish!
640
646
  request.emit(:response, response)
@@ -643,7 +649,7 @@ module HTTPX
643
649
  build_altsvc_connection(alt_origin, origin, alt_params)
644
650
  end
645
651
 
646
- parser.on(:pong, &method(:send_pending))
652
+ parser.on(:pong, &method(:pong))
647
653
 
648
654
  parser.on(:promise) do |request, stream|
649
655
  request.emit(:promise, parser, stream)
@@ -755,16 +761,6 @@ module HTTPX
755
761
  return if @inflight.positive? || @parser.waiting_for_ping?
756
762
  when :closing
757
763
  return unless connecting? || @state == :open
758
-
759
- unless @write_buffer.empty?
760
- # preset state before handshake, as error callbacks
761
- # may take it back here.
762
- @state = nextstate
763
- # handshakes, try sending
764
- consume
765
- @write_buffer.clear
766
- return
767
- end
768
764
  when :closed
769
765
  return unless @state == :closing
770
766
  return unless @write_buffer.empty?
@@ -790,6 +786,12 @@ module HTTPX
790
786
  case nextstate
791
787
  when :inactive
792
788
  disconnect
789
+ when :closing
790
+ return if @write_buffer.empty?
791
+
792
+ # try flushing termination handshakes
793
+ consume
794
+ @write_buffer.clear
793
795
  when :closed
794
796
  # TODO: should this raise an error instead?
795
797
  return unless @pending.empty?
@@ -813,16 +815,18 @@ module HTTPX
813
815
  end
814
816
 
815
817
  def close_sibling
816
- return unless @sibling
818
+ sibling = @sibling
817
819
 
818
- if @sibling.io_connected?
820
+ return unless sibling
821
+
822
+ if sibling.io_connected?
819
823
  reset
820
824
  # TODO: transition connection to closed
821
825
  end
822
826
 
823
- unless @sibling.state == :closed
824
- merge(@sibling) unless @main_sibling
825
- @sibling.force_reset(true)
827
+ unless sibling.state == :closed
828
+ merge(sibling) unless @main_sibling
829
+ sibling.force_reset(true)
826
830
  end
827
831
 
828
832
  @sibling = nil
@@ -902,28 +906,61 @@ module HTTPX
902
906
  end
903
907
  end
904
908
 
909
+ def ping
910
+ return if parser.waiting_for_ping?
911
+
912
+ parser.ping
913
+ call
914
+ end
915
+
916
+ def pong
917
+ @response_received_at = Utils.now
918
+ @no_more_requests_counter = 0
919
+ send_pending
920
+ end
921
+
922
+ def no_more_requests_loop_check
923
+ log(level: 3) { "NO MORE REQUESTS..." }
924
+ @no_more_requests_counter += 1
925
+
926
+ return if @no_more_requests_counter < 50
927
+
928
+ raise Error, "connection corrupted, aborted after looping for a while, " \
929
+ "please report this https://gitlab.com/os85/httpx/-/work_items " \
930
+ "along with debug logs"
931
+ end
932
+
905
933
  # recover internal state and emit all relevant error responses when +error+ was raised.
906
934
  # this takes an optiona +request+ which may have already been handled and can be opted out
907
935
  # in the state recovery process.
908
936
  def handle_error(error, request = nil)
909
- parser.handle_error(error, request) if @parser && @parser.respond_to?(:handle_error)
910
- while (req = @pending.shift)
911
- next if request && req == request
937
+ if request
938
+ @inflight -= 1
939
+ response = ErrorResponse.new(request, error)
940
+ request.response = response
941
+ request.emit(:response, response)
942
+ end
912
943
 
913
- response = ErrorResponse.new(req, error)
914
- req.response = response
915
- req.emit(:response, response)
944
+ pending = @pending
945
+ if (parser = @parser) && parser.respond_to?(:handle_error)
946
+ # parser.handle_error may disconnect the connection
947
+ pending = @pending.dup
948
+ @pending = []
949
+
950
+ parser.handle_error(error, request)
916
951
  end
917
952
 
918
- return unless request
953
+ while (req = pending.shift)
954
+ next if request && req == request
919
955
 
920
- @inflight -= 1
921
- response = ErrorResponse.new(request, error)
922
- request.response = response
923
- request.emit(:response, response)
956
+ resp = ErrorResponse.new(req, error)
957
+ req.response = resp
958
+ req.emit(:response, resp)
959
+ end
924
960
  end
925
961
 
926
962
  def set_request_timeouts(request)
963
+ request.connection = self
927
964
  set_request_write_timeout(request)
928
965
  set_request_read_timeout(request)
929
966
  set_request_request_timeout(request)
@@ -959,29 +996,29 @@ module HTTPX
959
996
  end
960
997
  end
961
998
 
962
- def write_timeout_callback(request, write_timeout)
999
+ def write_timeout_callback(request, timeout)
963
1000
  return if request.state == :done
964
1001
 
965
1002
  @write_buffer.clear
966
- error = WriteTimeoutError.new(request, nil, write_timeout)
1003
+ error = WriteTimeoutError.new(request, nil, timeout)
967
1004
 
968
- on_error(error, request)
1005
+ request.handle_error(error)
969
1006
  end
970
1007
 
971
- def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
1008
+ def read_timeout_callback(request, timeout, error_type = ReadTimeoutError)
972
1009
  response = request.response
973
1010
 
974
1011
  return if response && response.finished?
975
1012
 
976
1013
  @write_buffer.clear
977
- error = error_type.new(request, request.response, read_timeout)
1014
+ error = error_type.new(request, response, timeout)
978
1015
 
979
- on_error(error, request)
1016
+ request.handle_error(error)
980
1017
  end
981
1018
 
982
1019
  def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
983
1020
  request.set_timeout_callback(start_event) do
984
- unless @current_selector
1021
+ unless (selector = @current_selector)
985
1022
  raise Error, "request has been resend to an out-of-session connection, and this " \
986
1023
  "should never happen!!! Please report this error! " \
987
1024
  "(state:#{@state}, " \
@@ -992,7 +1029,7 @@ module HTTPX
992
1029
  "coalesced?:#{coalesced?})"
993
1030
  end
994
1031
 
995
- timer = @current_selector.after(timeout, callback)
1032
+ timer = selector.after(timeout, callback)
996
1033
  request.active_timeouts << label
997
1034
 
998
1035
  Array(finish_events).each do |event|
data/lib/httpx/io/ssl.rb CHANGED
@@ -51,6 +51,7 @@ module HTTPX
51
51
  end
52
52
 
53
53
  def protocol
54
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
54
55
  @io.alpn_protocol || super
55
56
  rescue StandardError
56
57
  super
@@ -60,6 +61,7 @@ module HTTPX
60
61
  # in jruby, alpn_protocol may return ""
61
62
  # https://github.com/jruby/jruby-openssl/issues/287
62
63
  def protocol
64
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
63
65
  proto = @io.alpn_protocol
64
66
 
65
67
  return super if proto.nil? || proto.empty?
@@ -76,9 +78,10 @@ module HTTPX
76
78
 
77
79
  def verify_hostname(host)
78
80
  return false if @ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
79
- return false if !@io.respond_to?(:peer_cert) || @io.peer_cert.nil?
81
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
82
+ return false if !@io.respond_to?(:peer_cert) || (peer_cert = @io.peer_cert).nil?
80
83
 
81
- OpenSSL::SSL.verify_certificate_identity(@io.peer_cert, host)
84
+ OpenSSL::SSL.verify_certificate_identity(peer_cert, host)
82
85
  end
83
86
 
84
87
  def connected?
@@ -86,7 +89,9 @@ module HTTPX
86
89
  end
87
90
 
88
91
  def ssl_session_expired?
89
- @ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
92
+ ssl_session = @ssl_session
93
+
94
+ ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (ssl_session.time.to_f + ssl_session.timeout)
90
95
  end
91
96
 
92
97
  def connect
@@ -97,6 +102,8 @@ module HTTPX
97
102
  return unless @state == :connected
98
103
  end
99
104
 
105
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
106
+
100
107
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
101
108
  if (hostname_is_ip = (@ip == @sni_hostname)) && @ctx.verify_hostname
102
109
  # IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
@@ -105,16 +112,19 @@ module HTTPX
105
112
  @ctx.verify_hostname = false
106
113
  end
107
114
 
108
- @io = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
115
+ ssl = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
109
116
 
110
- @io.hostname = @sni_hostname unless hostname_is_ip
111
- @io.session = @ssl_session unless ssl_session_expired?
112
- @io.sync_close = true
117
+ ssl.hostname = @sni_hostname unless hostname_is_ip
118
+ ssl.session = @ssl_session unless ssl_session_expired?
119
+ ssl.sync_close = true
120
+
121
+ @io = ssl
113
122
  end
114
123
  try_ssl_connect
115
124
  end
116
125
 
117
126
  def try_ssl_connect
127
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
118
128
  ret = @io.connect_nonblock(exception: false)
119
129
  log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
120
130
  case ret
@@ -147,7 +157,9 @@ module HTTPX
147
157
  def log_transition_state(nextstate)
148
158
  return super unless nextstate == :negotiated
149
159
 
150
- server_cert = @io.peer_cert
160
+ # @type ivar @io: OpenSSL::SSL::SSLSocket
161
+
162
+ server_cert = @io.peer_cert #: OpenSSL::X509::Certificate
151
163
 
152
164
  "#{super}\n\n" \
153
165
  "SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
data/lib/httpx/io/tcp.rb CHANGED
@@ -23,18 +23,21 @@ module HTTPX
23
23
  @fallback_protocol = @options.fallback_protocol
24
24
  @port = origin.port
25
25
  @interests = :w
26
- if @options.io
27
- @io = case @options.io
28
- when Hash
29
- @options.io[origin.authority]
30
- else
31
- @options.io
32
- end
33
- raise Error, "Given IO objects do not match the request authority" unless @io
34
-
35
- _, _, _, ip = @io.addr
36
- @ip = Resolver::Entry.new(ip)
37
- @addresses << @ip
26
+ if (io = @options.io)
27
+ io =
28
+ case io
29
+ when Hash
30
+ io[origin.authority]
31
+ else
32
+ io
33
+ end
34
+ raise Error, "Given IO objects do not match the request authority" unless io
35
+
36
+ # @type var io: TCPSocket | OpenSSL::SSL::SSLSocket
37
+
38
+ _, _, _, ip = io.addr
39
+ @io = io
40
+ @addresses << (@ip = Resolver::Entry.new(ip))
38
41
  @keep_open = true
39
42
  @state = :connected
40
43
  else
@@ -183,6 +186,9 @@ module HTTPX
183
186
 
184
187
  begin
185
188
  @io.close
189
+ rescue StandardError => e
190
+ log { "error closing socket" }
191
+ log { e.full_message(highlight: false) }
186
192
  ensure
187
193
  transition(:closed)
188
194
  end
data/lib/httpx/io/unix.rb CHANGED
@@ -14,16 +14,20 @@ module HTTPX
14
14
  @state = :idle
15
15
  @options = options
16
16
  @fallback_protocol = @options.fallback_protocol
17
- if @options.io
18
- @io = case @options.io
19
- when Hash
20
- @options.io[origin.authority]
21
- else
22
- @options.io
23
- end
24
- raise Error, "Given IO objects do not match the request authority" unless @io
17
+ if (io = @options.io)
18
+ io =
19
+ case io
20
+ when Hash
21
+ io[origin.authority]
22
+ else
23
+ io
24
+ end
25
+ raise Error, "Given IO objects do not match the request authority" unless io
26
+
27
+ # @type var io: UNIXSocket
25
28
 
26
- @path = @io.path
29
+ _, @path = io.addr
30
+ @io = io
27
31
  @keep_open = true
28
32
  @state = :connected
29
33
  elsif path
data/lib/httpx/options.rb CHANGED
@@ -202,16 +202,19 @@ module HTTPX
202
202
 
203
203
  REQUEST_BODY_IVARS = %i[@headers].freeze
204
204
 
205
- def ==(other)
206
- super || options_equals?(other)
207
- end
205
+ # checks whether +other+ matches the same connection-level options
206
+ def connection_options_match?(other, ignore_ivars = nil)
207
+ return true if self == other
208
208
 
209
- # checks whether +other+ is equal by comparing the session options
210
- def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
211
209
  # headers and other request options do not play a role, as they are
212
210
  # relevant only for the request.
213
- ivars = instance_variables - ignore_ivars
214
- other_ivars = other.instance_variables - ignore_ivars
211
+ ivars = instance_variables
212
+ ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
213
+ ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
214
+
215
+ other_ivars = other.instance_variables
216
+ other_ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
217
+ other_ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
215
218
 
216
219
  return false if ivars.size != other_ivars.size
217
220
 
@@ -222,6 +225,19 @@ module HTTPX
222
225
  end
223
226
  end
224
227
 
228
+ RESOLVER_IVARS = %i[
229
+ @resolver_class @resolver_cache @resolver_options
230
+ @resolver_native_class @resolver_system_class @resolver_https_class
231
+ ].freeze
232
+
233
+ # checks whether +other+ matches the same resolver-level options
234
+ def resolver_options_match?(other)
235
+ self == other ||
236
+ RESOLVER_IVARS.all? do |ivar|
237
+ instance_variable_get(ivar) == other.instance_variable_get(ivar)
238
+ end
239
+ end
240
+
225
241
  # returns a HTTPX::Options instance resulting of the merging of +other+ with self.
226
242
  # it may return self if +other+ is self or equal to self.
227
243
  def merge(other)
@@ -14,6 +14,8 @@ module HTTPX
14
14
  @state = :idle
15
15
  @buffer = "".b
16
16
  @headers = {}
17
+ @content_length = nil
18
+ @_has_trailers = @upgrade = false
17
19
  end
18
20
 
19
21
  def <<(chunk)
@@ -25,7 +27,8 @@ module HTTPX
25
27
  @state = :idle
26
28
  @headers = {}
27
29
  @content_length = nil
28
- @_has_trailers = nil
30
+ @_has_trailers = @upgrade = false
31
+ @buffer = @buffer.to_s
29
32
  @buffer.clear
30
33
  end
31
34
 
@@ -34,7 +37,7 @@ module HTTPX
34
37
  end
35
38
 
36
39
  def upgrade_data
37
- @buffer
40
+ @buffer.to_s
38
41
  end
39
42
 
40
43
  private
@@ -55,6 +58,7 @@ module HTTPX
55
58
  end
56
59
 
57
60
  def parse_headline
61
+ #: @type ivar @buffer: String
58
62
  idx = @buffer.index("\n")
59
63
  return unless idx
60
64
 
@@ -75,6 +79,8 @@ module HTTPX
75
79
  headers = @headers
76
80
  buffer = @buffer
77
81
 
82
+ #: @type var buffer: String
83
+
78
84
  while (idx = buffer.index("\n"))
79
85
  # @type var line: String
80
86
  line = buffer.byteslice(0..idx)
@@ -118,17 +124,20 @@ module HTTPX
118
124
 
119
125
  def parse_data
120
126
  if @buffer.respond_to?(:each)
127
+ # @type ivar @buffer: Transcoder::Chunker::Decoder
121
128
  @buffer.each do |chunk|
122
129
  @observer.on_data(chunk)
123
130
  end
124
131
  elsif @content_length
125
- # @type var data: String
132
+ # @type ivar @buffer: String
126
133
  data = @buffer.byteslice(0, @content_length)
134
+ # @type var data: String
127
135
  @buffer = @buffer.byteslice(@content_length..-1) || "".b
128
136
  @content_length -= data.bytesize
129
137
  @observer.on_data(data)
130
138
  data.clear
131
139
  else
140
+ # @type ivar @buffer: String
132
141
  @observer.on_data(@buffer)
133
142
  @buffer.clear
134
143
  end
@@ -152,7 +161,7 @@ module HTTPX
152
161
  tr_encoding.split(/ *, */).each do |encoding|
153
162
  case encoding
154
163
  when "chunked"
155
- @buffer = Transcoder::Chunker::Decoder.new(@buffer, @_has_trailers)
164
+ @buffer = Transcoder::Chunker::Decoder.new(@buffer.to_s, @_has_trailers)
156
165
  end
157
166
  end
158
167
  end
@@ -165,6 +174,7 @@ module HTTPX
165
174
  if @content_length
166
175
  @content_length <= 0
167
176
  elsif @buffer.respond_to?(:finished?)
177
+ # @type ivar @buffer: Transcoder::Chunker::Decoder
168
178
  @buffer.finished?
169
179
  else
170
180
  false
@@ -44,6 +44,7 @@ module HTTPX
44
44
  super
45
45
 
46
46
  @auth_header_value = nil
47
+ @auth_header_value_mtx = Thread::Mutex.new
47
48
  @skip_auth_header_value = false
48
49
  end
49
50
 
@@ -63,7 +64,9 @@ module HTTPX
63
64
  end
64
65
 
65
66
  def reset_auth_header_value!
66
- @auth_header_value = nil
67
+ @auth_header_value_mtx.synchronize do
68
+ @auth_header_value = nil
69
+ end
67
70
  end
68
71
 
69
72
  private
@@ -71,9 +74,11 @@ module HTTPX
71
74
  def send_request(request, *)
72
75
  return super if @skip_auth_header_value || request.authorized?
73
76
 
74
- @auth_header_value ||= generate_auth_token
77
+ auth_header_value = @auth_header_value_mtx.synchronize do
78
+ @auth_header_value ||= generate_auth_token
79
+ end
75
80
 
76
- request.authorize(@auth_header_value) if @auth_header_value
81
+ request.authorize(auth_header_value) if auth_header_value
77
82
 
78
83
  super
79
84
  end
@@ -92,9 +97,11 @@ module HTTPX
92
97
  end
93
98
 
94
99
  module RequestMethods
100
+ attr_reader :auth_token_value
101
+
95
102
  def initialize(*)
96
103
  super
97
- @auth_token_value = nil
104
+ @auth_token_value = @auth_header_value = nil
98
105
  end
99
106
 
100
107
  def authorized?
@@ -102,19 +109,20 @@ module HTTPX
102
109
  end
103
110
 
104
111
  def unauthorize!
105
- return unless (auth_value = @auth_token_value)
112
+ return unless (auth_value = @auth_header_value)
106
113
 
107
114
  @headers.get("authorization").delete(auth_value)
108
115
 
109
- @auth_token_value = nil
116
+ @auth_token_value = @auth_header_value = nil
110
117
  end
111
118
 
112
119
  def authorize(auth_value)
120
+ @auth_header_value = auth_value
113
121
  if (auth_type = @options.auth_header_type)
114
- auth_value = "#{auth_type} #{auth_value}"
122
+ @auth_header_value = "#{auth_type} #{@auth_header_value}"
115
123
  end
116
124
 
117
- @headers.add("authorization", auth_value)
125
+ @headers.add("authorization", @auth_header_value)
118
126
 
119
127
  @auth_token_value = auth_value
120
128
  end
@@ -138,8 +146,14 @@ module HTTPX
138
146
  return unless auth_error?(response, request.options) ||
139
147
  (@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
140
148
 
149
+ # regenerate token before retry, but only if it's the first request from batch failing.
150
+ # otherwise, it means that the first request already passed here, so this request should
151
+ # use whatever was generated for it.
152
+ @auth_header_value_mtx.synchronize do
153
+ @auth_header_value = generate_auth_token if request.auth_token_value == @auth_header_value
154
+ end
155
+
141
156
  request.unauthorize!
142
- @auth_header_value = generate_auth_token
143
157
  end
144
158
 
145
159
  def auth_error?(response, options)
@@ -148,7 +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
- if (retry_after = when_to_retry(request, response, options))
151
+ if (retry_after = when_to_retry(request, response, options)) && retry_after.positive?
152
152
  # apply jitter
153
153
  if (jitter = request.options.retry_jitter)
154
154
  retry_after = jitter.call(retry_after)
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 = @drainer = @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
 
@@ -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
@@ -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
@@ -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
data/lib/httpx/session.rb CHANGED
@@ -373,7 +373,11 @@ module HTTPX
373
373
  resolver = find_resolver_for(connection, selector)
374
374
 
375
375
  pin(connection, selector)
376
- early_resolve(resolver, connection) || resolver.lazy_resolve(connection)
376
+ if early_resolve(resolver, connection)
377
+ @pool.checkin_resolver(resolver)
378
+ else
379
+ resolver.lazy_resolve(connection)
380
+ end
377
381
  end
378
382
 
379
383
  def early_resolve(resolver, connection)
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "1.7.5"
4
+ VERSION = "1.7.6"
5
5
  end
data/sig/altsvc.rbs CHANGED
@@ -3,6 +3,8 @@ module HTTPX
3
3
  module ConnectionMixin
4
4
  H2_ALTSVC_SCHEMES: Array[String]
5
5
 
6
+ ALTSVC_IGNORE_IVARS: Array[Symbol]
7
+
6
8
  def send: (Request request) -> void
7
9
 
8
10
  def match?: (http_uri uri, Options options) -> bool
@@ -52,7 +52,7 @@ module HTTPX
52
52
 
53
53
  def on_complete: () -> void
54
54
 
55
- def dispatch: () -> void
55
+ def dispatch: (Request request) -> void
56
56
 
57
57
  def ping: () -> void
58
58
 
data/sig/connection.rbs CHANGED
@@ -50,6 +50,7 @@ module HTTPX
50
50
  @altsvc_connection: instance?
51
51
  @sibling: instance?
52
52
  @main_sibling: bool
53
+ @no_more_requests_counter: Integer
53
54
 
54
55
 
55
56
  def addresses: () -> Array[Resolver::Entry]?
@@ -156,6 +157,12 @@ module HTTPX
156
157
 
157
158
  def build_socket: (?Array[Resolver::Entry]? addrs) -> (TCP | SSL | UNIX)
158
159
 
160
+ def ping: () -> void
161
+
162
+ def pong: () -> void
163
+
164
+ def no_more_requests_loop_check: () -> void
165
+
159
166
  def handle_error: (StandardError error, ?Request? request) -> void
160
167
 
161
168
  def force_purge: () -> void
@@ -172,9 +179,9 @@ module HTTPX
172
179
 
173
180
  def set_request_request_timeout: (Request request) -> void
174
181
 
175
- def write_timeout_callback: (Request request, Numeric write_timeout) -> void
182
+ def write_timeout_callback: (Request request, Numeric timeout) -> void
176
183
 
177
- def read_timeout_callback: (Request request, Numeric read_timeout, ?singleton(RequestTimeoutError) error_type) -> void
184
+ def read_timeout_callback: (Request request, Numeric timeout, ?singleton(RequestTimeoutError) error_type) -> void
178
185
 
179
186
  def set_request_timeout: (Symbol label, Request request, Numeric timeout, Symbol start_event, Symbol | Array[Symbol] finish_events) { () -> void } -> void
180
187
 
data/sig/io/ssl.rbs CHANGED
@@ -8,6 +8,7 @@ module HTTPX
8
8
 
9
9
  @ctx: OpenSSL::SSL::SSLContext
10
10
  @verify_hostname: bool
11
+ @sni_hostname: String
11
12
 
12
13
  attr_writer ssl_session: OpenSSL::SSL::Session?
13
14
 
data/sig/io/tcp.rbs CHANGED
@@ -46,7 +46,7 @@ module HTTPX
46
46
 
47
47
  public
48
48
 
49
- def read: (Integer size, ?(Buffer | String) buffer) -> (0 | nil | untyped)
49
+ def read: (Integer size, Buffer | String buffer) -> (0 | nil | untyped)
50
50
 
51
51
  def write: (Buffer buffer) -> Integer?
52
52
 
@@ -67,6 +67,6 @@ module HTTPX
67
67
 
68
68
  def do_transition: (Symbol nextstate) -> void
69
69
 
70
- def log_transition_state: (Symbol nextstate) -> void
70
+ def log_transition_state: (Symbol nextstate) -> String
71
71
  end
72
72
  end
data/sig/options.rbs CHANGED
@@ -17,6 +17,7 @@ module HTTPX
17
17
 
18
18
  DEFAULT_OPTIONS: Hash[Symbol, untyped]
19
19
  REQUEST_BODY_IVARS: Array[Symbol]
20
+ RESOLVER_IVARS: Array[Symbol]
20
21
  RESOLVER_TYPES: Array[Symbol]
21
22
  USER_AGENT: String
22
23
 
@@ -106,8 +107,10 @@ module HTTPX
106
107
 
107
108
  attr_reader ssl: Hash[Symbol, untyped]
108
109
 
109
- # io
110
- type io_option = _ToIO | Hash[String, _ToIO]
110
+ type external_io = TCPSocket | OpenSSL::SSL::SSLSocket | UNIXSocket
111
+
112
+ type io_option = external_io | Hash[String, external_io]
113
+
111
114
  attr_reader io: io_option?
112
115
 
113
116
  # fallback_protocol
@@ -142,7 +145,9 @@ module HTTPX
142
145
 
143
146
  def ==: (Options other) -> bool
144
147
 
145
- def options_equals?: (Options other, ?Array[Symbol] ignore_ivars) -> bool
148
+ def connection_options_match?: (Options other, ?Array[Symbol] ignore_ivars) -> bool
149
+
150
+ def resolver_options_match?: (Options other) -> bool
146
151
 
147
152
  def merge: (Object & _ToHash[Symbol, untyped] other) -> (instance | self)
148
153
 
data/sig/parser/http1.rbs CHANGED
@@ -23,7 +23,7 @@ module HTTPX
23
23
 
24
24
  @observer: _HTTP1Events
25
25
  @state: Symbol
26
- @buffer: String
26
+ @buffer: String | Transcoder::Chunker::Decoder
27
27
  @content_type: String?
28
28
  @content_length: Integer
29
29
  @_has_trailers: bool
data/sig/plugins/auth.rbs CHANGED
@@ -10,7 +10,8 @@ module HTTPX
10
10
  end
11
11
 
12
12
  module InstanceMethods
13
- @auth_header_value: String
13
+ @auth_header_value: String?
14
+ @auth_header_value_mtx: Thread::Mutex
14
15
  @skip_auth_header_value: bool
15
16
 
16
17
  def authorization: (?string token, ?auth_header_type: string) ?{ (Request) -> string } -> instance
@@ -29,7 +30,9 @@ module HTTPX
29
30
  end
30
31
 
31
32
  module RequestMethods
32
- @auth_token_value: String?
33
+ attr_reader auth_token_value: String?
34
+
35
+ @auth_header_value: String?
33
36
 
34
37
  def authorized?: () -> bool
35
38
 
@@ -47,7 +47,7 @@ module HTTPX
47
47
 
48
48
  def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
49
49
 
50
- def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
50
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> Numeric?
51
51
  end
52
52
 
53
53
  module RequestMethods
data/sig/pool.rbs CHANGED
@@ -34,7 +34,7 @@ module HTTPX
34
34
 
35
35
  def checkout_resolver: (Options options) -> resolver_manager
36
36
 
37
- def checkin_resolver: (Resolver::Resolver resolver) -> void
37
+ def checkin_resolver: (Resolver::Multi | Resolver::Resolver resolver) -> void
38
38
 
39
39
  private
40
40
 
data/sig/request.rbs CHANGED
@@ -18,6 +18,7 @@ module HTTPX
18
18
  attr_reader drain_error: StandardError?
19
19
  attr_reader active_timeouts: Array[Symbol]
20
20
 
21
+ attr_writer connection: Connection?
21
22
  attr_accessor peer_address: (String | IPAddr)?
22
23
 
23
24
  attr_writer persistent: bool
@@ -81,6 +82,8 @@ module HTTPX
81
82
 
82
83
  def set_timeout_callback: (Symbol event) { (*untyped) -> void } -> void
83
84
 
85
+ def handle_error: (StandardError error) -> void
86
+
84
87
  private
85
88
 
86
89
  def initialize_body: (Options options) -> Transcoder::_Encoder?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.5
4
+ version: 1.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -165,6 +165,7 @@ extra_rdoc_files:
165
165
  - doc/release_notes/1_7_3.md
166
166
  - doc/release_notes/1_7_4.md
167
167
  - doc/release_notes/1_7_5.md
168
+ - doc/release_notes/1_7_6.md
168
169
  files:
169
170
  - LICENSE.txt
170
171
  - README.md
@@ -302,6 +303,7 @@ files:
302
303
  - doc/release_notes/1_7_3.md
303
304
  - doc/release_notes/1_7_4.md
304
305
  - doc/release_notes/1_7_5.md
306
+ - doc/release_notes/1_7_6.md
305
307
  - lib/httpx.rb
306
308
  - lib/httpx/adapters/datadog.rb
307
309
  - lib/httpx/adapters/faraday.rb