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 +4 -4
- data/doc/release_notes/1_7_4.md +1 -1
- data/doc/release_notes/1_7_6.md +24 -0
- data/lib/httpx/adapters/datadog.rb +14 -1
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +20 -17
- data/lib/httpx/connection/http2.rb +10 -3
- data/lib/httpx/connection.rb +78 -41
- data/lib/httpx/io/ssl.rb +20 -8
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -4
- data/lib/httpx/plugins/auth.rb +23 -9
- data/lib/httpx/plugins/retries.rb +1 -1
- data/lib/httpx/pool.rb +7 -9
- data/lib/httpx/request.rb +15 -3
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +12 -9
- data/lib/httpx/session.rb +5 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection.rbs +9 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth.rbs +5 -2
- data/sig/plugins/retries.rbs +1 -1
- data/sig/pool.rbs +1 -1
- data/sig/request.rbs +3 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b34abe93b42862837d9131524d6e9663a93474f70c65ac2a10946eb62c9aeb14
|
|
4
|
+
data.tar.gz: '0853b845878005ace796106f79b735d9d75e205b7ab28c03a3786b53233d6a4b'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0be425ba5468990b31fb3fe04b97ee8c727cc1f16690decc2057314c3645e5baa21fae9b54585bc6e628410374dbfd9b8676df3aaed180b321a9b59cbbad2853
|
|
7
|
+
data.tar.gz: 3cbe9b0e2952c7d330bffe727f2b478f53152d431a306d48f5f41cb5033e9ba7fa2ddec9a319aed49f051c8e9e6e2d8e489ecccc83a6c622246ef6922c9bbecc
|
data/doc/release_notes/1_7_4.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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 =
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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?
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(:
|
|
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
|
-
|
|
818
|
+
sibling = @sibling
|
|
817
819
|
|
|
818
|
-
|
|
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
|
|
824
|
-
merge(
|
|
825
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
953
|
+
while (req = pending.shift)
|
|
954
|
+
next if request && req == request
|
|
919
955
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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,
|
|
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,
|
|
1003
|
+
error = WriteTimeoutError.new(request, nil, timeout)
|
|
967
1004
|
|
|
968
|
-
|
|
1005
|
+
request.handle_error(error)
|
|
969
1006
|
end
|
|
970
1007
|
|
|
971
|
-
def read_timeout_callback(request,
|
|
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,
|
|
1014
|
+
error = error_type.new(request, response, timeout)
|
|
978
1015
|
|
|
979
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
ssl = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 =
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
214
|
-
|
|
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)
|
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -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
|
-
@
|
|
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
|
-
|
|
77
|
+
auth_header_value = @auth_header_value_mtx.synchronize do
|
|
78
|
+
@auth_header_value ||= generate_auth_token
|
|
79
|
+
end
|
|
75
80
|
|
|
76
|
-
request.authorize(
|
|
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 = @
|
|
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
|
-
|
|
122
|
+
@auth_header_value = "#{auth_type} #{@auth_header_value}"
|
|
115
123
|
end
|
|
116
124
|
|
|
117
|
-
@headers.add("authorization",
|
|
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
|
|
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
|
-
|
|
157
|
+
if resolver.is_a?(Resolver::Multi)
|
|
158
|
+
resolver_class = resolver.resolvers.first.class
|
|
159
|
+
else
|
|
160
|
+
resolver_class = resolver.class
|
|
164
161
|
|
|
165
|
-
|
|
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
|
|
data/lib/httpx/response.rb
CHANGED
|
@@ -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
|
|
304
|
+
def finish!
|
|
305
|
+
@request.connection = nil
|
|
306
|
+
end
|
|
303
307
|
|
|
304
308
|
# raises the wrapped exception.
|
|
305
309
|
def raise_for_status
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -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) &&
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
if READABLE.include?(interests)
|
|
158
|
+
r = r.nil? ? io : (Array(r) << io)
|
|
159
|
+
end
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
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)
|
|
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
data/sig/altsvc.rbs
CHANGED
data/sig/connection/http1.rbs
CHANGED
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
|
|
182
|
+
def write_timeout_callback: (Request request, Numeric timeout) -> void
|
|
176
183
|
|
|
177
|
-
def read_timeout_callback: (Request request, Numeric
|
|
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
data/sig/io/tcp.rbs
CHANGED
|
@@ -46,7 +46,7 @@ module HTTPX
|
|
|
46
46
|
|
|
47
47
|
public
|
|
48
48
|
|
|
49
|
-
def read: (Integer size,
|
|
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) ->
|
|
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
|
-
|
|
110
|
-
|
|
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
|
|
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
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
|
-
|
|
33
|
+
attr_reader auth_token_value: String?
|
|
34
|
+
|
|
35
|
+
@auth_header_value: String?
|
|
33
36
|
|
|
34
37
|
def authorized?: () -> bool
|
|
35
38
|
|
data/sig/plugins/retries.rbs
CHANGED
|
@@ -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) ->
|
|
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
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.
|
|
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
|