httpx 1.8.4 → 1.9.1
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_8_1.md +1 -1
- data/doc/release_notes/1_8_2.md +4 -4
- data/doc/release_notes/1_8_3.md +3 -5
- data/doc/release_notes/1_8_4.md +1 -1
- data/doc/release_notes/1_9_0.md +75 -0
- data/doc/release_notes/1_9_1.md +32 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +15 -2
- data/lib/httpx/altsvc.rb +29 -15
- data/lib/httpx/chainable.rb +4 -1
- data/lib/httpx/connection/http1.rb +34 -35
- data/lib/httpx/connection/http2.rb +146 -33
- data/lib/httpx/connection.rb +282 -162
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +42 -21
- data/lib/httpx/options.rb +86 -35
- data/lib/httpx/parser/http1.rb +3 -6
- data/lib/httpx/plugins/auth/digest.rb +1 -2
- data/lib/httpx/plugins/auth.rb +11 -2
- data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
- data/lib/httpx/plugins/aws_sigv4.rb +1 -1
- data/lib/httpx/plugins/basic_auth.rb +2 -0
- data/lib/httpx/plugins/cache.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +8 -6
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/content_digest.rb +3 -5
- data/lib/httpx/plugins/digest_auth.rb +2 -0
- data/lib/httpx/plugins/expect.rb +10 -1
- data/lib/httpx/plugins/fiber_concurrency.rb +59 -16
- data/lib/httpx/plugins/follow_redirects.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +21 -0
- data/lib/httpx/plugins/h2c.rb +12 -11
- data/lib/httpx/plugins/hedging.rb +284 -0
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +2 -2
- data/lib/httpx/plugins/proxy/http.rb +67 -26
- data/lib/httpx/plugins/proxy/socks5.rb +4 -4
- data/lib/httpx/plugins/proxy/ssh.rb +30 -11
- data/lib/httpx/plugins/proxy.rb +68 -54
- data/lib/httpx/plugins/push_promise.rb +4 -2
- data/lib/httpx/plugins/query.rb +1 -0
- data/lib/httpx/plugins/retries.rb +2 -2
- data/lib/httpx/plugins/server_sent_events.rb +1 -0
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +12 -10
- data/lib/httpx/plugins/stream_bidi.rb +4 -0
- data/lib/httpx/plugins/tracing.rb +99 -11
- data/lib/httpx/plugins/upgrade/h2.rb +3 -3
- data/lib/httpx/plugins/upgrade.rb +2 -0
- data/lib/httpx/plugins/webdav.rb +8 -0
- data/lib/httpx/request/body.rb +2 -1
- data/lib/httpx/request.rb +6 -6
- data/lib/httpx/resolver/https.rb +3 -2
- data/lib/httpx/resolver/native.rb +19 -9
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver/system.rb +4 -0
- data/lib/httpx/resolver.rb +10 -6
- data/lib/httpx/response.rb +19 -7
- data/lib/httpx/selector.rb +111 -10
- data/lib/httpx/session.rb +36 -10
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/altsvc.rbs +3 -3
- data/sig/callbacks.rbs +1 -0
- data/sig/chainable.rbs +3 -1
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +23 -17
- data/sig/connection.rbs +29 -7
- data/sig/headers.rbs +1 -1
- data/sig/httpx.rbs +2 -0
- data/sig/io/tcp.rbs +5 -0
- data/sig/options.rbs +43 -12
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +0 -1
- data/sig/plugins/auth.rbs +12 -5
- data/sig/plugins/expect.rbs +6 -0
- data/sig/plugins/fiber_concurrency.rbs +10 -1
- data/sig/plugins/hedging.rbs +44 -0
- data/sig/plugins/proxy/http.rbs +9 -2
- data/sig/plugins/proxy.rbs +3 -2
- data/sig/plugins/retries.rbs +2 -2
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/tracing.rbs +22 -5
- data/sig/request/body.rbs +1 -1
- data/sig/resolver/entry.rbs +2 -2
- data/sig/resolver/https.rbs +2 -0
- data/sig/resolver/native.rbs +3 -0
- data/sig/resolver/resolver.rbs +1 -1
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +1 -1
- data/sig/response.rbs +1 -2
- data/sig/selector.rbs +17 -3
- data/sig/session.rbs +8 -6
- metadata +7 -1
|
@@ -50,11 +50,11 @@ module HTTPX
|
|
|
50
50
|
|
|
51
51
|
attr_reader :streams, :pending
|
|
52
52
|
|
|
53
|
-
def initialize(buffer, options)
|
|
54
|
-
@callbacks = nil
|
|
53
|
+
def initialize(buffer, pending, options)
|
|
54
|
+
@callbacks = @last_error = nil
|
|
55
55
|
@options = options
|
|
56
56
|
@settings = @options.http2_settings
|
|
57
|
-
@pending =
|
|
57
|
+
@pending = pending
|
|
58
58
|
@streams = {}
|
|
59
59
|
@drains = {}
|
|
60
60
|
@pings = []
|
|
@@ -68,16 +68,21 @@ module HTTPX
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def timeout
|
|
71
|
-
return @options.timeout
|
|
71
|
+
return @options.timeout.operation_timeout if @handshake_completed
|
|
72
72
|
|
|
73
|
-
@options.timeout
|
|
73
|
+
@options.timeout.settings_timeout
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def interests
|
|
77
|
-
if @connection.closed
|
|
77
|
+
if @connection.state == :closed
|
|
78
78
|
return unless @handshake_completed
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
if @buffer.empty?
|
|
81
|
+
# a closed HTTP/2 connection can still receive DATA frames from inflight streams.
|
|
82
|
+
return :r unless @streams.empty?
|
|
83
|
+
|
|
84
|
+
return
|
|
85
|
+
end
|
|
81
86
|
|
|
82
87
|
# HTTP/2 GOAWAY frame buffered.
|
|
83
88
|
return :w
|
|
@@ -85,7 +90,7 @@ module HTTPX
|
|
|
85
90
|
|
|
86
91
|
unless @connection.state == :connected && @handshake_completed
|
|
87
92
|
# HTTP/2 in intermediate state or still completing initialization-
|
|
88
|
-
return @buffer.empty? ? :r : :
|
|
93
|
+
return @buffer.empty? ? :r : :w
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
unless @connection.send_buffer.empty?
|
|
@@ -98,26 +103,69 @@ module HTTPX
|
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
# there are pending bufferable requests
|
|
101
|
-
|
|
106
|
+
if !@pending.empty? && can_buffer_pending_requests?
|
|
107
|
+
# only wait for writable if not waiting on the peer.
|
|
108
|
+
return waiting_for_peer? ? :rw : :w
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
unless @drains.empty?
|
|
112
|
+
return :w unless @buffer.empty?
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
streams_with_no_window = @drains.none? do |stream, _|
|
|
115
|
+
# if an existing stream has buffered bytes, it must wait for
|
|
116
|
+
# WINDOW_UPDATE frames
|
|
117
|
+
stream.send_buffer.empty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
return streams_with_no_window ? :r : :rw
|
|
121
|
+
|
|
122
|
+
end
|
|
105
123
|
|
|
106
124
|
if @buffer.empty?
|
|
107
125
|
# skip if no more requests or pings to process
|
|
108
|
-
return
|
|
126
|
+
return unless waiting_for_peer?
|
|
109
127
|
|
|
110
128
|
:r
|
|
111
129
|
else
|
|
112
130
|
# buffered frames
|
|
113
|
-
:w
|
|
131
|
+
waiting_for_peer? ? :rw : :w
|
|
114
132
|
end
|
|
115
133
|
end
|
|
116
134
|
|
|
117
135
|
def close
|
|
118
|
-
|
|
136
|
+
if @connection.closed?
|
|
137
|
+
# if there are inflight streams waiting to be completed AFTER the peer alread shut down the
|
|
138
|
+
# connection from their end, raise-or-retry depending on whether the error is recoverable.
|
|
139
|
+
if (ex = @last_error)
|
|
140
|
+
if @streams.any? || @pending.any?
|
|
141
|
+
if ex.is_a?(GoawayError) && !ex.unrecoverable?
|
|
142
|
+
inflight_unprocessed_requests = @streams.map do |req, _|
|
|
143
|
+
req.transition(:idle)
|
|
144
|
+
req
|
|
145
|
+
end
|
|
146
|
+
@streams.clear
|
|
147
|
+
|
|
148
|
+
# only signal exhaustion when inflight requests had to be moved back.
|
|
149
|
+
unless inflight_unprocessed_requests.empty?
|
|
150
|
+
@pending.unshift(*inflight_unprocessed_requests)
|
|
151
|
+
emit(:exhausted, ex)
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
while (req, _ = @streams.shift)
|
|
155
|
+
emit(:error, req, ex)
|
|
156
|
+
end
|
|
157
|
+
while (req = @pending.shift)
|
|
158
|
+
emit(:error, req, ex)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# clean pending payload now that everything's gone.
|
|
164
|
+
@buffer.clear
|
|
165
|
+
end
|
|
166
|
+
else
|
|
119
167
|
@connection.goaway
|
|
120
|
-
emit(:timeout, @options.timeout
|
|
168
|
+
emit(:timeout, @options.timeout.close_handshake_timeout)
|
|
121
169
|
end
|
|
122
170
|
emit(:close)
|
|
123
171
|
end
|
|
@@ -138,10 +186,16 @@ module HTTPX
|
|
|
138
186
|
# data received by the peer.
|
|
139
187
|
emit_stream_error(stream, request, error)
|
|
140
188
|
end
|
|
189
|
+
|
|
190
|
+
# if the connection transitioned to :closed, start tearing it down. Explicitly bypass the
|
|
191
|
+
# :closing transition to give it time to complete the inflight streams.
|
|
192
|
+
return unless @connection.state == :closed
|
|
193
|
+
|
|
194
|
+
close
|
|
141
195
|
end
|
|
142
196
|
|
|
143
197
|
def send(request, head = false)
|
|
144
|
-
unless
|
|
198
|
+
unless can_buffer_pending_requests?
|
|
145
199
|
head ? @pending.unshift(request) : @pending << request
|
|
146
200
|
return false
|
|
147
201
|
end
|
|
@@ -150,6 +204,7 @@ module HTTPX
|
|
|
150
204
|
handle_stream(stream, request)
|
|
151
205
|
@streams[request] = stream
|
|
152
206
|
@max_requests -= 1
|
|
207
|
+
emit(:request_accepted, request)
|
|
153
208
|
end
|
|
154
209
|
handle(request, stream)
|
|
155
210
|
true
|
|
@@ -158,6 +213,7 @@ module HTTPX
|
|
|
158
213
|
false
|
|
159
214
|
rescue ::HTTP2::Error::Error, ArgumentError => e
|
|
160
215
|
emit(:error, request, e)
|
|
216
|
+
false
|
|
161
217
|
end
|
|
162
218
|
|
|
163
219
|
def consume
|
|
@@ -166,6 +222,8 @@ module HTTPX
|
|
|
166
222
|
|
|
167
223
|
handle(request, stream)
|
|
168
224
|
end
|
|
225
|
+
|
|
226
|
+
send_pending
|
|
169
227
|
end
|
|
170
228
|
|
|
171
229
|
def handle_error(ex, request = nil)
|
|
@@ -173,35 +231,60 @@ module HTTPX
|
|
|
173
231
|
case ex
|
|
174
232
|
when OperationTimeoutError
|
|
175
233
|
if !@handshake_completed && @connection.state != :closed
|
|
234
|
+
# timed out waiting for HTTP/2 connection handshake to complete.
|
|
176
235
|
@connection.goaway(:settings_timeout, "closing due to settings timeout")
|
|
177
236
|
emit(:close_handshake)
|
|
178
237
|
settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
|
|
179
238
|
settings_ex.set_backtrace(ex.backtrace)
|
|
180
239
|
ex = settings_ex
|
|
240
|
+
elsif @connection.state == :closing
|
|
241
|
+
# failed to receive data for inflight streams in time.
|
|
242
|
+
ex = @last_error || ex
|
|
243
|
+
end
|
|
244
|
+
when EOFError
|
|
245
|
+
if @connection.state == :closing
|
|
246
|
+
# socket closed while waiting for inflight streams to finish
|
|
247
|
+
ex = @last_error || ex
|
|
181
248
|
end
|
|
182
249
|
when GoawayError
|
|
183
250
|
last_stream_id = ex.last_stream_id
|
|
184
251
|
end
|
|
185
252
|
|
|
186
253
|
inflight_unprocessed_requests = [] #: Array[Request]
|
|
254
|
+
unprocessed_streams = [] #: Array[::HTTP2::Stream]
|
|
187
255
|
|
|
188
|
-
|
|
189
|
-
|
|
256
|
+
@streams.delete_if do |req, stream|
|
|
257
|
+
# allow inflight streams to finish
|
|
258
|
+
next(false) if !last_stream_id.infinite? && stream.id <= last_stream_id
|
|
259
|
+
|
|
260
|
+
@drains.delete(stream)
|
|
261
|
+
|
|
262
|
+
next(true) if request && request == req
|
|
190
263
|
|
|
191
264
|
if stream.id > last_stream_id
|
|
192
265
|
req.transition(:idle)
|
|
193
266
|
# unprocessed request
|
|
194
267
|
inflight_unprocessed_requests << req
|
|
268
|
+
unprocessed_streams << stream
|
|
195
269
|
|
|
196
|
-
next
|
|
270
|
+
next(true)
|
|
197
271
|
end
|
|
198
272
|
|
|
199
273
|
emit(:error, req, ex)
|
|
274
|
+
true
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
@pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
|
|
278
|
+
|
|
279
|
+
unless @streams.empty?
|
|
280
|
+
unprocessed_streams.each(&:close)
|
|
281
|
+
|
|
282
|
+
# streams covered by the GOAWAY are still being processed.
|
|
283
|
+
return
|
|
200
284
|
end
|
|
201
285
|
|
|
202
286
|
if ex.is_a?(GoawayError) && !ex.unrecoverable?
|
|
203
287
|
# resend unprocessed requests on a different connection
|
|
204
|
-
@pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
|
|
205
288
|
emit(:exhausted, ex) if @pending.any?
|
|
206
289
|
return
|
|
207
290
|
end
|
|
@@ -224,18 +307,29 @@ module HTTPX
|
|
|
224
307
|
@pings.any?
|
|
225
308
|
end
|
|
226
309
|
|
|
310
|
+
def no_more_requests?
|
|
311
|
+
@streams.empty?
|
|
312
|
+
end
|
|
313
|
+
|
|
227
314
|
def reset_requests; end
|
|
228
315
|
|
|
229
316
|
private
|
|
230
317
|
|
|
231
|
-
|
|
232
|
-
|
|
318
|
+
# whether there are PING acks, or streams waiting for more data
|
|
319
|
+
# (that can be a response or WINDOW_UPDATE frames).
|
|
320
|
+
def waiting_for_peer?
|
|
321
|
+
waiting_for_ping? || @streams.any? { |r, s| !r.can_buffer? || !s.send_buffer.empty? }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def can_buffer_pending_requests?
|
|
325
|
+
!@connection.closed? &&
|
|
326
|
+
(@handshake_completed || !@wait_for_handshake) &&
|
|
233
327
|
@streams.size < @max_concurrent_requests &&
|
|
234
328
|
@streams.size < @max_requests
|
|
235
329
|
end
|
|
236
330
|
|
|
237
331
|
def send_pending
|
|
238
|
-
while (request = @pending.shift)
|
|
332
|
+
while !@buffer.full? && can_buffer_pending_requests? && (request = @pending.shift)
|
|
239
333
|
break unless send(request, true)
|
|
240
334
|
end
|
|
241
335
|
end
|
|
@@ -322,13 +416,23 @@ module HTTPX
|
|
|
322
416
|
def join_body(stream, request)
|
|
323
417
|
return if request.body.empty?
|
|
324
418
|
|
|
325
|
-
chunk
|
|
419
|
+
# the next chunk waits until the peer grants more window.
|
|
420
|
+
throw(:buffer_full) if @drains.key?(stream) && (!stream.send_buffer.empty? || !@connection.send_buffer.empty?)
|
|
421
|
+
|
|
422
|
+
chunk = @drains.delete(stream) || request.drain_body
|
|
326
423
|
while chunk
|
|
327
424
|
next_chunk = request.drain_body
|
|
328
425
|
send_chunk(request, stream, chunk, next_chunk)
|
|
329
426
|
|
|
330
|
-
if next_chunk && (
|
|
331
|
-
|
|
427
|
+
if next_chunk && (
|
|
428
|
+
# send window is full
|
|
429
|
+
@buffer.full? ||
|
|
430
|
+
# stream / chunked request, must write a chunk at a time
|
|
431
|
+
request.body.unbounded_body? ||
|
|
432
|
+
# peer window is full, must wait for WINDOW_UPDATE frame(s)
|
|
433
|
+
!stream.send_buffer.empty? || !@connection.send_buffer.empty?
|
|
434
|
+
)
|
|
435
|
+
@drains[stream] = next_chunk
|
|
332
436
|
throw(:buffer_full)
|
|
333
437
|
end
|
|
334
438
|
|
|
@@ -462,7 +566,10 @@ module HTTPX
|
|
|
462
566
|
end
|
|
463
567
|
end
|
|
464
568
|
end
|
|
465
|
-
|
|
569
|
+
|
|
570
|
+
consume
|
|
571
|
+
|
|
572
|
+
send(@pending.shift, true) unless @pending.empty?
|
|
466
573
|
|
|
467
574
|
return unless @streams.empty? && exhausted?
|
|
468
575
|
|
|
@@ -494,16 +601,21 @@ module HTTPX
|
|
|
494
601
|
emit(:error, request, error)
|
|
495
602
|
end
|
|
496
603
|
else
|
|
497
|
-
|
|
604
|
+
# keep goaway error around, in case inflight streams never complete.
|
|
605
|
+
@last_error = ex = GoawayError.new(error, last_stream_id)
|
|
498
606
|
ex.set_backtrace(caller)
|
|
499
607
|
|
|
500
608
|
handle_error(ex)
|
|
501
|
-
teardown
|
|
502
609
|
end
|
|
503
610
|
end
|
|
504
|
-
return unless is_connection_closed
|
|
611
|
+
return unless is_connection_closed
|
|
505
612
|
|
|
506
|
-
|
|
613
|
+
if @streams.empty?
|
|
614
|
+
emit(:close)
|
|
615
|
+
else
|
|
616
|
+
# this connection should not start new streams.
|
|
617
|
+
emit(:closing)
|
|
618
|
+
end
|
|
507
619
|
end
|
|
508
620
|
|
|
509
621
|
def on_frame_sent(frame)
|
|
@@ -590,8 +702,9 @@ module HTTPX
|
|
|
590
702
|
|
|
591
703
|
def teardown(request = nil)
|
|
592
704
|
if request
|
|
593
|
-
@
|
|
594
|
-
|
|
705
|
+
if (stream = @streams.delete(request))
|
|
706
|
+
@drains.delete(stream)
|
|
707
|
+
end
|
|
595
708
|
else
|
|
596
709
|
@drains.clear
|
|
597
710
|
@streams.clear
|