httpx 1.9.0 → 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 +7 -7
- data/doc/release_notes/1_9_1.md +32 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +14 -1
- data/lib/httpx/connection/http1.rb +24 -17
- data/lib/httpx/connection/http2.rb +108 -29
- data/lib/httpx/connection.rb +158 -162
- data/lib/httpx/io/tcp.rb +24 -7
- data/lib/httpx/plugins/aws_sigv4.rb +0 -1
- data/lib/httpx/plugins/cache.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +1 -0
- data/lib/httpx/plugins/content_digest.rb +3 -5
- data/lib/httpx/plugins/expect.rb +8 -1
- data/lib/httpx/plugins/fiber_concurrency.rb +56 -19
- data/lib/httpx/plugins/h2c.rb +12 -11
- data/lib/httpx/plugins/hedging.rb +2 -2
- data/lib/httpx/plugins/ntlm_auth.rb +4 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +4 -3
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -16
- data/lib/httpx/plugins/proxy/socks5.rb +4 -4
- data/lib/httpx/plugins/proxy.rb +8 -15
- data/lib/httpx/plugins/query.rb +1 -0
- data/lib/httpx/plugins/stream.rb +12 -10
- data/lib/httpx/plugins/stream_bidi.rb +4 -0
- data/lib/httpx/plugins/tracing.rb +62 -21
- data/lib/httpx/plugins/upgrade/h2.rb +2 -2
- data/lib/httpx/request.rb +1 -1
- 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 +5 -5
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +108 -7
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +8 -2
- data/sig/connection.rbs +11 -6
- data/sig/io/tcp.rbs +5 -0
- data/sig/plugins/expect.rbs +6 -0
- data/sig/plugins/fiber_concurrency.rbs +10 -1
- data/sig/plugins/proxy/http.rbs +3 -1
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/tracing.rbs +10 -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/selector.rbs +15 -1
- data/sig/session.rbs +1 -1
- metadata +3 -1
data/lib/httpx/connection.rb
CHANGED
|
@@ -39,9 +39,7 @@ module HTTPX
|
|
|
39
39
|
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling,
|
|
40
40
|
:read_buffer, :write_buffer
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
attr_accessor :current_session, :family
|
|
42
|
+
attr_accessor :current_selector, :current_session, :family
|
|
45
43
|
|
|
46
44
|
protected :ssl_session, :sibling, :read_buffer, :write_buffer
|
|
47
45
|
|
|
@@ -65,7 +63,6 @@ module HTTPX
|
|
|
65
63
|
@read_buffer = Buffer.new(@options.buffer_size)
|
|
66
64
|
@write_buffer = Buffer.new(@options.buffer_size)
|
|
67
65
|
@pending = []
|
|
68
|
-
@inflight = 0
|
|
69
66
|
@keep_alive_timeout = @options.timeout.keep_alive_timeout
|
|
70
67
|
@no_more_requests_counter = 0
|
|
71
68
|
|
|
@@ -95,7 +92,6 @@ module HTTPX
|
|
|
95
92
|
@origins = orig.origins.dup
|
|
96
93
|
@read_buffer = orig.read_buffer.dup
|
|
97
94
|
@write_buffer = orig.write_buffer.dup
|
|
98
|
-
@inflight = 0
|
|
99
95
|
@pending = []
|
|
100
96
|
transition(:idle)
|
|
101
97
|
|
|
@@ -134,7 +130,7 @@ module HTTPX
|
|
|
134
130
|
end
|
|
135
131
|
|
|
136
132
|
def match?(uri, options)
|
|
137
|
-
return false if
|
|
133
|
+
return false if @state == :closing || (!used? && @state == :closed)
|
|
138
134
|
|
|
139
135
|
@origins.include?(uri.origin) &&
|
|
140
136
|
# if there is more than one origin to match, it means that this connection
|
|
@@ -148,6 +144,9 @@ module HTTPX
|
|
|
148
144
|
def mergeable?(connection)
|
|
149
145
|
return false if @state == :closing || @state == :closed || !@io
|
|
150
146
|
|
|
147
|
+
# do not coalesce with HEv2 sibling
|
|
148
|
+
return false if @sibling.equal?(connection)
|
|
149
|
+
|
|
151
150
|
return false unless connection.addresses
|
|
152
151
|
|
|
153
152
|
(
|
|
@@ -198,24 +197,13 @@ module HTTPX
|
|
|
198
197
|
# returns the number of pending requests both in the connection as well as parser
|
|
199
198
|
# pending queues
|
|
200
199
|
def pending_count
|
|
201
|
-
|
|
202
|
-
count += @parser.pending.size if @parser.respond_to?(:pending)
|
|
203
|
-
count
|
|
200
|
+
@pending.size
|
|
204
201
|
end
|
|
205
202
|
|
|
206
203
|
def purge_pending(&block)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
pending.reject! do |req|
|
|
211
|
-
block.call(req)
|
|
212
|
-
true
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
@pending.reject! do |req|
|
|
216
|
-
block.call(req)
|
|
217
|
-
true
|
|
218
|
-
end
|
|
204
|
+
pending = @pending.dup
|
|
205
|
+
@pending.clear
|
|
206
|
+
pending.each(&block)
|
|
219
207
|
end
|
|
220
208
|
|
|
221
209
|
def io_connected?
|
|
@@ -228,6 +216,11 @@ module HTTPX
|
|
|
228
216
|
@state == :idle
|
|
229
217
|
end
|
|
230
218
|
|
|
219
|
+
# whether the connection can be removed from the selector.
|
|
220
|
+
def closed_for_select?
|
|
221
|
+
@state == :closed
|
|
222
|
+
end
|
|
223
|
+
|
|
231
224
|
def inflight?
|
|
232
225
|
@parser && (
|
|
233
226
|
# parser may be dealing with other requests (possibly started from a different fiber)
|
|
@@ -272,7 +265,7 @@ module HTTPX
|
|
|
272
265
|
|
|
273
266
|
consume
|
|
274
267
|
when :closed
|
|
275
|
-
return if
|
|
268
|
+
return if no_requests_to_handle?
|
|
276
269
|
|
|
277
270
|
# there are pending requests to send, restart the state machine.
|
|
278
271
|
idling
|
|
@@ -283,8 +276,18 @@ module HTTPX
|
|
|
283
276
|
|
|
284
277
|
call
|
|
285
278
|
when :closing
|
|
279
|
+
# if bytes are available, that's the client GOAWAY frame. Otherwise, the peer closed it.
|
|
280
|
+
sending_goaway = !@write_buffer.empty?
|
|
281
|
+
|
|
286
282
|
consume
|
|
287
|
-
|
|
283
|
+
|
|
284
|
+
if sending_goaway
|
|
285
|
+
# parser may have inflight streams which need terminating.
|
|
286
|
+
@parser&.close
|
|
287
|
+
transition(:closed)
|
|
288
|
+
elsif @state == :closing
|
|
289
|
+
return
|
|
290
|
+
end
|
|
288
291
|
|
|
289
292
|
# @fiber-switch-guard
|
|
290
293
|
# fiber may have switch while closing @io.
|
|
@@ -329,6 +332,7 @@ module HTTPX
|
|
|
329
332
|
|
|
330
333
|
# @fiber-switch-guard
|
|
331
334
|
if @io.can_disconnect? && @pending.empty?
|
|
335
|
+
@state = :closed
|
|
332
336
|
disconnect
|
|
333
337
|
return
|
|
334
338
|
end
|
|
@@ -348,7 +352,7 @@ module HTTPX
|
|
|
348
352
|
if delete_pending
|
|
349
353
|
@pending.clear
|
|
350
354
|
elsif (parser = @parser)
|
|
351
|
-
|
|
355
|
+
parser.reset_requests if parser.respond_to?(:reset_requests)
|
|
352
356
|
return unless @pending.empty?
|
|
353
357
|
end
|
|
354
358
|
|
|
@@ -365,7 +369,7 @@ module HTTPX
|
|
|
365
369
|
end
|
|
366
370
|
|
|
367
371
|
def reset
|
|
368
|
-
return if @state == :
|
|
372
|
+
return if @state == :closed
|
|
369
373
|
|
|
370
374
|
# do not reset a connection which may have restarted back to :idle, such when the parser resets
|
|
371
375
|
# (example: HTTP/1 parser disabling pipelining)
|
|
@@ -376,6 +380,8 @@ module HTTPX
|
|
|
376
380
|
parser = @parser
|
|
377
381
|
|
|
378
382
|
if parser && parser.respond_to?(:max_concurrent_requests)
|
|
383
|
+
# @type var parser: HTTP1
|
|
384
|
+
|
|
379
385
|
# if connection being reset has at some downgraded the number of concurrent
|
|
380
386
|
# requests, such as in the case where an attempt to use HTTP/1 pipelining failed,
|
|
381
387
|
# keep that information around.
|
|
@@ -416,9 +422,14 @@ module HTTPX
|
|
|
416
422
|
def timeout
|
|
417
423
|
return if @state == :closed || @state == :inactive
|
|
418
424
|
|
|
419
|
-
|
|
425
|
+
timeout = @timeout || @options.timeout.operation_timeout
|
|
426
|
+
|
|
427
|
+
return timeout unless connecting? && (attempt_timeout = connect_attempt_time_left)
|
|
420
428
|
|
|
421
|
-
|
|
429
|
+
# the attempt may already be overdue
|
|
430
|
+
attempt_timeout = 0 unless attempt_timeout.positive?
|
|
431
|
+
|
|
432
|
+
timeout && timeout < attempt_timeout ? timeout : attempt_timeout
|
|
422
433
|
end
|
|
423
434
|
|
|
424
435
|
def idling
|
|
@@ -430,7 +441,7 @@ module HTTPX
|
|
|
430
441
|
transition(:idle)
|
|
431
442
|
return unless @parser
|
|
432
443
|
|
|
433
|
-
|
|
444
|
+
@parser.reset_requests if @parser.respond_to?(:reset_requests)
|
|
434
445
|
@parser = nil
|
|
435
446
|
end
|
|
436
447
|
|
|
@@ -439,6 +450,9 @@ module HTTPX
|
|
|
439
450
|
end
|
|
440
451
|
|
|
441
452
|
def deactivate
|
|
453
|
+
# safety net so that connection state does not corrupt.
|
|
454
|
+
clear_pending_requests
|
|
455
|
+
|
|
442
456
|
transition(:inactive)
|
|
443
457
|
end
|
|
444
458
|
|
|
@@ -480,6 +494,9 @@ module HTTPX
|
|
|
480
494
|
|
|
481
495
|
@current_session = @current_selector = nil
|
|
482
496
|
|
|
497
|
+
# NOTE: don't change state of the connection after this call!
|
|
498
|
+
# the connection is checked back into the pool, and may be checked out by
|
|
499
|
+
# another thread before the next statement.
|
|
483
500
|
current_session.deselect_connection(self, current_selector, @cloned)
|
|
484
501
|
end
|
|
485
502
|
|
|
@@ -507,16 +524,33 @@ module HTTPX
|
|
|
507
524
|
# they should not fail due to such errors.
|
|
508
525
|
return if @state == :inactive
|
|
509
526
|
|
|
510
|
-
|
|
527
|
+
attempt_timeout = connect_attempt_time_left if connecting?
|
|
528
|
+
|
|
529
|
+
current_timeout = @timeout
|
|
530
|
+
current_timeout ||= @options.timeout.operation_timeout unless @state == :closed
|
|
531
|
+
|
|
532
|
+
if current_timeout
|
|
511
533
|
# if @timeout isn't set, it's not a completion-based timeout, i.e.
|
|
512
534
|
# operation timeout; in such a case, we have to reset it once it piggybacks.
|
|
513
535
|
@reset_timeout_on_call ||= @timeout.nil?
|
|
514
536
|
|
|
515
537
|
@timeout = current_timeout - error.timeout
|
|
516
|
-
|
|
538
|
+
|
|
539
|
+
if @timeout.positive?
|
|
540
|
+
# if this was a connect timeout error and there are more addresses to try.
|
|
541
|
+
connect_next(error) if attempt_timeout && !attempt_timeout.positive?
|
|
542
|
+
return
|
|
543
|
+
end
|
|
517
544
|
|
|
518
545
|
@timeout = nil
|
|
519
546
|
@reset_timeout_on_call = false
|
|
547
|
+
elsif attempt_timeout
|
|
548
|
+
# when request timeouts are set instead.
|
|
549
|
+
connect_next(error) unless attempt_timeout.positive?
|
|
550
|
+
return
|
|
551
|
+
else
|
|
552
|
+
# the select interval came from another selectable, as this one has no timeout set.
|
|
553
|
+
return
|
|
520
554
|
end
|
|
521
555
|
|
|
522
556
|
error = error.to_connection_error if connecting?
|
|
@@ -537,6 +571,21 @@ module HTTPX
|
|
|
537
571
|
|
|
538
572
|
private
|
|
539
573
|
|
|
574
|
+
# the time left until the attempt to connect to the current address should be abandoned.
|
|
575
|
+
def connect_attempt_time_left
|
|
576
|
+
io = @io
|
|
577
|
+
|
|
578
|
+
return unless io.respond_to?(:connect_attempt_time_left)
|
|
579
|
+
|
|
580
|
+
io.connect_attempt_time_left
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def connect_next(error)
|
|
584
|
+
@io.next_address!(error)
|
|
585
|
+
|
|
586
|
+
connect
|
|
587
|
+
end
|
|
588
|
+
|
|
540
589
|
def try_connect
|
|
541
590
|
emit(:connecting)
|
|
542
591
|
connect
|
|
@@ -565,7 +614,6 @@ module HTTPX
|
|
|
565
614
|
# * the number of inflight requests
|
|
566
615
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
567
616
|
if no_more_requests? && @write_buffer.empty?
|
|
568
|
-
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
569
617
|
|
|
570
618
|
# terminate if an altsvc connection has been established
|
|
571
619
|
terminate if @altsvc_connection
|
|
@@ -608,21 +656,19 @@ module HTTPX
|
|
|
608
656
|
parser << @read_buffer.to_s
|
|
609
657
|
|
|
610
658
|
# continue reading if possible.
|
|
611
|
-
break if interests == :w && !epiped
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
break if @state == :closing || @state == :closed
|
|
659
|
+
break if (interests == :w && !epiped) ||
|
|
660
|
+
# exit the read loop if connection is closed
|
|
661
|
+
@state == :closed
|
|
615
662
|
|
|
616
663
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
617
664
|
if no_more_requests? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
618
|
-
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
619
665
|
|
|
620
666
|
# terminate if an altsvc connection has been established
|
|
621
667
|
terminate if @altsvc_connection
|
|
622
668
|
|
|
623
669
|
return
|
|
624
670
|
end
|
|
625
|
-
end unless ((ints = interests).nil? || ints == :w
|
|
671
|
+
end unless ((ints = interests).nil? || ints == :w) && !epiped
|
|
626
672
|
|
|
627
673
|
#
|
|
628
674
|
# tight write loop.
|
|
@@ -633,7 +679,7 @@ module HTTPX
|
|
|
633
679
|
# buffer has been drained, mark and exit the write loop.
|
|
634
680
|
if @write_buffer.empty?
|
|
635
681
|
# we only mark as drained on the first loop
|
|
636
|
-
write_drained = write_drained.nil? &&
|
|
682
|
+
write_drained = write_drained.nil? && !@parser&.no_more_requests?
|
|
637
683
|
|
|
638
684
|
break
|
|
639
685
|
end
|
|
@@ -668,11 +714,12 @@ module HTTPX
|
|
|
668
714
|
end
|
|
669
715
|
|
|
670
716
|
# exit write loop if marked to consume from peer, or is closing.
|
|
671
|
-
break if interests == :r || @state == :
|
|
717
|
+
break if (ints = interests) == :r || @state == :closed
|
|
672
718
|
|
|
673
719
|
write_drained = false
|
|
674
720
|
end unless (ints = interests) == :r
|
|
675
721
|
|
|
722
|
+
# fill the write buffer with more requests
|
|
676
723
|
send_pending if @state == :open
|
|
677
724
|
|
|
678
725
|
# return if socket is drained
|
|
@@ -689,8 +736,9 @@ module HTTPX
|
|
|
689
736
|
end
|
|
690
737
|
|
|
691
738
|
def send_pending
|
|
692
|
-
while
|
|
693
|
-
|
|
739
|
+
while (request = @pending.shift)
|
|
740
|
+
# request may have been rerouted to the pending list
|
|
741
|
+
break unless send_request_to_parser(request, true)
|
|
694
742
|
end
|
|
695
743
|
end
|
|
696
744
|
|
|
@@ -698,44 +746,29 @@ module HTTPX
|
|
|
698
746
|
@parser ||= build_parser
|
|
699
747
|
end
|
|
700
748
|
|
|
701
|
-
def send_request_to_parser(request)
|
|
702
|
-
|
|
703
|
-
request.peer_address = @io.ip && @io.ip.address
|
|
704
|
-
set_request_timeouts(request)
|
|
705
|
-
|
|
706
|
-
parser.send(request)
|
|
749
|
+
def send_request_to_parser(request, head = false)
|
|
750
|
+
request.peer_address = @io&.ip&.address
|
|
707
751
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
def enqueue_pending_requests_from_parser(parser)
|
|
718
|
-
parser.reset_requests # move sequential requests back to pending queue.
|
|
719
|
-
parser_pending_requests = parser.pending
|
|
720
|
-
|
|
721
|
-
return if parser_pending_requests.empty?
|
|
722
|
-
|
|
723
|
-
# the connection will be reused, so parser requests must come
|
|
724
|
-
# back to the pending list before the parser is reset.
|
|
725
|
-
@inflight -= parser_pending_requests.size
|
|
726
|
-
@pending.unshift(*parser_pending_requests)
|
|
752
|
+
if @state == :inactive
|
|
753
|
+
transition(:active)
|
|
754
|
+
# mark request as ping, as this inactive connection may have been
|
|
755
|
+
# closed by the server, and we don't want that to influence retry
|
|
756
|
+
# bookkeeping.
|
|
757
|
+
request.ping!
|
|
758
|
+
end
|
|
727
759
|
|
|
728
|
-
parser.
|
|
760
|
+
parser.send(request, head)
|
|
729
761
|
end
|
|
730
762
|
|
|
731
763
|
def build_parser(protocol = @io.protocol)
|
|
732
|
-
parser = parser_type(protocol).new(@write_buffer, @options)
|
|
764
|
+
parser = parser_type(protocol).new(@write_buffer, @pending, @options)
|
|
733
765
|
set_parser_callbacks(parser)
|
|
734
766
|
parser.max_concurrent_requests = @max_concurrent_requests if @max_concurrent_requests && parser.respond_to?(:max_concurrent_requests=)
|
|
735
767
|
parser
|
|
736
768
|
end
|
|
737
769
|
|
|
738
770
|
def set_parser_callbacks(parser)
|
|
771
|
+
parser.on(:request_accepted, &method(:set_request_timeouts))
|
|
739
772
|
parser.on(:response) do |request, response|
|
|
740
773
|
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
|
741
774
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
@@ -743,7 +776,6 @@ module HTTPX
|
|
|
743
776
|
@previously_exhausted_with_error = false
|
|
744
777
|
@response_received_at = Utils.now
|
|
745
778
|
@no_more_requests_counter = 0
|
|
746
|
-
@inflight -= 1
|
|
747
779
|
response.finish!
|
|
748
780
|
request.emit_response(response)
|
|
749
781
|
end
|
|
@@ -757,7 +789,7 @@ module HTTPX
|
|
|
757
789
|
request.emit(:promise, parser, stream)
|
|
758
790
|
end
|
|
759
791
|
parser.on(:exhausted) do |error|
|
|
760
|
-
|
|
792
|
+
parser.reset_requests
|
|
761
793
|
|
|
762
794
|
if error
|
|
763
795
|
if @previously_exhausted_with_error
|
|
@@ -786,6 +818,13 @@ module HTTPX
|
|
|
786
818
|
parser.on(:origin) do |origin|
|
|
787
819
|
@origins |= [origin]
|
|
788
820
|
end
|
|
821
|
+
parser.on(:closing) do
|
|
822
|
+
# this happens if the peer server sent a GOAWAY frame while there are inflight
|
|
823
|
+
# streams; moving to :closing ensures that new requests won't be sent to this
|
|
824
|
+
# connection, while keeping it around to complete unfinished work
|
|
825
|
+
reset_ping_timer
|
|
826
|
+
transition(:closing)
|
|
827
|
+
end
|
|
789
828
|
parser.on(:close) do
|
|
790
829
|
reset
|
|
791
830
|
end
|
|
@@ -793,15 +832,16 @@ module HTTPX
|
|
|
793
832
|
consume unless @state == :closed
|
|
794
833
|
end
|
|
795
834
|
parser.on(:reset) do
|
|
796
|
-
|
|
835
|
+
parser.reset_requests
|
|
797
836
|
|
|
798
837
|
reset
|
|
799
838
|
|
|
800
839
|
next unless @state == :closed
|
|
801
840
|
|
|
841
|
+
# @fiber-switch-guard
|
|
802
842
|
# :reset event only fired in http/1.1, so this guarantees
|
|
803
843
|
# that the connection will be closed here.
|
|
804
|
-
|
|
844
|
+
call unless no_more_requests?
|
|
805
845
|
end
|
|
806
846
|
parser.on(:current_timeout) do
|
|
807
847
|
@current_timeout = @timeout = parser.timeout
|
|
@@ -809,29 +849,33 @@ module HTTPX
|
|
|
809
849
|
parser.on(:timeout) do |tout|
|
|
810
850
|
@timeout = tout
|
|
811
851
|
end
|
|
812
|
-
parser.on(:
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
parser.close
|
|
818
|
-
|
|
819
|
-
request.transition(:idle)
|
|
820
|
-
current_session.__send__(:send_request, request, current_selector,
|
|
821
|
-
@options.merge(ssl: { alpn_protocols: %w[http/1.1] })) do |other_conn|
|
|
822
|
-
other_conn.merge(self)
|
|
823
|
-
end
|
|
824
|
-
next
|
|
825
|
-
when OperationTimeoutError
|
|
826
|
-
# request level timeouts should take precedence
|
|
827
|
-
next unless request.active_timeouts.empty?
|
|
828
|
-
end
|
|
852
|
+
parser.on(:keep_alive_timeout) do |tout|
|
|
853
|
+
@keep_alive_timeout = tout
|
|
854
|
+
end
|
|
855
|
+
parser.on(:error, &method(:on_parser_error))
|
|
856
|
+
end
|
|
829
857
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
858
|
+
def on_parser_error(request, error)
|
|
859
|
+
case error
|
|
860
|
+
when :http_1_1_required
|
|
861
|
+
current_session = @current_session
|
|
862
|
+
current_selector = @current_selector
|
|
863
|
+
parser.close
|
|
864
|
+
|
|
865
|
+
request.transition(:idle)
|
|
866
|
+
current_session.__send__(:send_request, request, current_selector,
|
|
867
|
+
@options.merge(ssl: { alpn_protocols: %w[http/1.1] })) do |other_conn|
|
|
868
|
+
other_conn.merge(self)
|
|
869
|
+
end
|
|
870
|
+
return
|
|
871
|
+
when OperationTimeoutError
|
|
872
|
+
# request level timeouts should take precedence
|
|
873
|
+
return unless request.active_timeouts.empty?
|
|
834
874
|
end
|
|
875
|
+
|
|
876
|
+
response = ErrorResponse.new(request, error)
|
|
877
|
+
request.response = response
|
|
878
|
+
request.emit_response(response)
|
|
835
879
|
end
|
|
836
880
|
|
|
837
881
|
def transition(nextstate)
|
|
@@ -882,21 +926,10 @@ module HTTPX
|
|
|
882
926
|
emit(:open)
|
|
883
927
|
when :inactive
|
|
884
928
|
return unless @state == :open
|
|
885
|
-
|
|
886
|
-
# @type ivar @parser: HTTP1 | HTTP2
|
|
887
|
-
|
|
888
|
-
# do not deactivate connection in use
|
|
889
|
-
return if @inflight.positive? || @parser.waiting_for_ping?
|
|
890
929
|
when :closing
|
|
891
930
|
return unless connecting? || @state == :open
|
|
892
931
|
when :closed
|
|
893
|
-
return unless @state == :closing
|
|
894
|
-
return unless @write_buffer.empty?
|
|
895
|
-
|
|
896
|
-
purge_after_closed
|
|
897
|
-
|
|
898
|
-
# @fiber-switch-guard
|
|
899
|
-
return unless @state == :closing && (@io.nil? || @io.can_disconnect?)
|
|
932
|
+
return unless @state == :closing && @write_buffer.empty?
|
|
900
933
|
when :already_open
|
|
901
934
|
nextstate = :open
|
|
902
935
|
# the first check for given io readiness must still use a timeout.
|
|
@@ -926,8 +959,10 @@ module HTTPX
|
|
|
926
959
|
consume
|
|
927
960
|
@write_buffer.clear
|
|
928
961
|
when :closed
|
|
929
|
-
|
|
930
|
-
|
|
962
|
+
purge_after_closed
|
|
963
|
+
|
|
964
|
+
# @fiber-switch-guard
|
|
965
|
+
return unless no_more_requests?
|
|
931
966
|
|
|
932
967
|
disconnect
|
|
933
968
|
end
|
|
@@ -1092,81 +1127,42 @@ module HTTPX
|
|
|
1092
1127
|
"coalesced?:#{coalesced?})"
|
|
1093
1128
|
end
|
|
1094
1129
|
|
|
1095
|
-
def
|
|
1096
|
-
|
|
1097
|
-
@no_more_requests_counter += 1
|
|
1098
|
-
|
|
1099
|
-
return if @no_more_requests_counter < 50
|
|
1100
|
-
|
|
1101
|
-
per_request_state = @parser.pending.map do |req|
|
|
1102
|
-
"[state:#{req.state},ping?:#{req.ping?}]"
|
|
1103
|
-
end.join(",")
|
|
1104
|
-
|
|
1105
|
-
parser_state =
|
|
1106
|
-
case parser
|
|
1107
|
-
when HTTP2
|
|
1108
|
-
parser_pending_requests = parser.pending.map do |r|
|
|
1109
|
-
num_retries = r.respond_to(:retries) ? r.retries : nil
|
|
1110
|
-
req_conn = r.instance_variable_get(:@connection)
|
|
1111
|
-
"id:#{r.object_id},num_retries:#{num_retries}," \
|
|
1112
|
-
"is-same-conn:#{req_conn.equal?(self)},started?:#{r.started?}"
|
|
1113
|
-
end.join(",")
|
|
1114
|
-
+"parser-streams:#{parser.streams.size}," \
|
|
1115
|
-
"parser-handshake-completed:#{parser.instance_variable_get(:@handshake_completed)}," \
|
|
1116
|
-
"parser-wait-for-handshake:#{parser.instance_variable_get(:@wait_for_handshake)}," \
|
|
1117
|
-
"parser-pending:[#{parser_pending_requests}],"
|
|
1118
|
-
else
|
|
1119
|
-
+""
|
|
1120
|
-
end
|
|
1121
|
-
|
|
1122
|
-
parser_state << "parser-requests:#{per_request_state}"
|
|
1123
|
-
|
|
1124
|
-
raise Error, "connection corrupted, aborted after looping for a while, " \
|
|
1125
|
-
"please report this https://gitlab.com/os85/httpx/-/work_items " \
|
|
1126
|
-
"along with debug logs\n(" \
|
|
1127
|
-
"state:#{@state},inflight:#{@inflight},pending:#{@pending.size}," \
|
|
1128
|
-
"exhausted:#{@exhausted}," \
|
|
1129
|
-
"previously-exhausted-with-error:#{@previously_exhausted_with_error}," \
|
|
1130
|
-
"connected_for_secs:#{Utils.elapsed_time(@connected_at) if @connected_at}," \
|
|
1131
|
-
"num-origins:#{@origins.size}," \
|
|
1132
|
-
"last_response_received:#{Utils.elapsed_time(@response_received_at) if @response_received_at}," \
|
|
1133
|
-
"#{parser_state}," \
|
|
1134
|
-
"in-selector:#{!!@current_selector}," \
|
|
1135
|
-
"in-session:#{!!@current_session}" \
|
|
1136
|
-
")"
|
|
1130
|
+
def clear_pending_requests
|
|
1131
|
+
@pending.clear
|
|
1137
1132
|
end
|
|
1138
1133
|
|
|
1139
1134
|
# true when there are no more pending nor inflight (in parser) requests
|
|
1140
1135
|
def no_more_requests?
|
|
1141
|
-
@pending.empty? &&
|
|
1136
|
+
@pending.empty? && (
|
|
1137
|
+
connecting? ||
|
|
1138
|
+
@parser.nil? ||
|
|
1139
|
+
!@parser.respond_to?(:no_more_requests?) ||
|
|
1140
|
+
@parser.no_more_requests?
|
|
1141
|
+
)
|
|
1142
1142
|
end
|
|
1143
1143
|
|
|
1144
|
+
alias_method :no_requests_to_handle?, :no_more_requests?
|
|
1144
1145
|
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
1145
1146
|
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
1146
1147
|
# in the state recovery process.
|
|
1147
1148
|
def handle_error(error, request = nil)
|
|
1148
1149
|
if request
|
|
1149
|
-
@inflight -= 1
|
|
1150
1150
|
response = ErrorResponse.new(request, error)
|
|
1151
1151
|
request.response = response
|
|
1152
1152
|
request.emit_response(response)
|
|
1153
1153
|
end
|
|
1154
1154
|
|
|
1155
|
-
pending = @pending
|
|
1156
1155
|
if (parser = @parser) && parser.respond_to?(:handle_error)
|
|
1157
1156
|
# parser.handle_error may disconnect the connection
|
|
1158
|
-
pending = @pending.dup
|
|
1159
|
-
@pending = []
|
|
1160
|
-
|
|
1161
1157
|
parser.handle_error(error, request)
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
next if request && req == request
|
|
1158
|
+
else
|
|
1159
|
+
while (req = @pending.shift)
|
|
1160
|
+
next if request && req == request
|
|
1166
1161
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1162
|
+
resp = ErrorResponse.new(req, error)
|
|
1163
|
+
req.response = resp
|
|
1164
|
+
req.emit_response(resp)
|
|
1165
|
+
end
|
|
1170
1166
|
end
|
|
1171
1167
|
end
|
|
1172
1168
|
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -17,7 +17,7 @@ module HTTPX
|
|
|
17
17
|
@keep_open = false
|
|
18
18
|
@addresses = []
|
|
19
19
|
@ip_index = -1
|
|
20
|
-
@ip = nil
|
|
20
|
+
@ip = @attempt_started_at = nil
|
|
21
21
|
@hostname = origin.host
|
|
22
22
|
@options = options
|
|
23
23
|
@fallback_protocol = @options.fallback_protocol
|
|
@@ -80,6 +80,27 @@ module HTTPX
|
|
|
80
80
|
@addresses.any?
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# time left until the attempt to connect to the current address should be abandoned.
|
|
84
|
+
def connect_attempt_time_left
|
|
85
|
+
return unless @state == :idle && @ip_index.positive? && (attempt_started_at = @attempt_started_at)
|
|
86
|
+
|
|
87
|
+
timeouts = @options.timeout
|
|
88
|
+
timeout = timeouts.connect_timeout || timeouts.request_timeout || timeouts.total_request_timeout
|
|
89
|
+
|
|
90
|
+
return unless timeout && timeout.finite?
|
|
91
|
+
|
|
92
|
+
timeout.fdiv(@addresses.size) - Utils.elapsed_time(attempt_started_at)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# abandons the connection attempt to the current address.
|
|
96
|
+
def next_address!(e)
|
|
97
|
+
@io&.close
|
|
98
|
+
@ip_index -= 1
|
|
99
|
+
raise e if @ip_index.negative?
|
|
100
|
+
|
|
101
|
+
log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
|
|
102
|
+
end
|
|
103
|
+
|
|
83
104
|
def to_io
|
|
84
105
|
@io.to_io
|
|
85
106
|
end
|
|
@@ -109,13 +130,8 @@ module HTTPX
|
|
|
109
130
|
try_connect
|
|
110
131
|
rescue Errno::EHOSTUNREACH,
|
|
111
132
|
Errno::ENETUNREACH => e
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
raise e if @ip_index.negative?
|
|
115
|
-
|
|
116
|
-
log { "failed connecting to #{@ip} (#{e.message}), evict from cache and trying next..." }
|
|
133
|
+
next_address!(e)
|
|
117
134
|
@options.resolver_cache.evict(@hostname, @ip)
|
|
118
|
-
|
|
119
135
|
@io = build_socket
|
|
120
136
|
retry
|
|
121
137
|
rescue Errno::ECONNREFUSED,
|
|
@@ -237,6 +253,7 @@ module HTTPX
|
|
|
237
253
|
|
|
238
254
|
def build_socket
|
|
239
255
|
@ip = @addresses[@ip_index]
|
|
256
|
+
@attempt_started_at = Utils.now
|
|
240
257
|
Socket.new(@ip.family, :STREAM, 0)
|
|
241
258
|
end
|
|
242
259
|
|
data/lib/httpx/plugins/cache.rb
CHANGED
|
@@ -106,7 +106,7 @@ module HTTPX
|
|
|
106
106
|
def fetch_response(request, *)
|
|
107
107
|
response = super
|
|
108
108
|
|
|
109
|
-
return unless response
|
|
109
|
+
return unless response && response.is_a?(Response)
|
|
110
110
|
|
|
111
111
|
if cacheable_request?(request) && cacheable_response?(request, response) && !response.cached?
|
|
112
112
|
log { "caching response for #{request.uri}..." }
|