httpx 1.4.0 → 1.4.4
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/README.md +1 -2
- data/doc/release_notes/1_4_1.md +19 -0
- data/doc/release_notes/1_4_2.md +20 -0
- data/doc/release_notes/1_4_3.md +11 -0
- data/doc/release_notes/1_4_4.md +14 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +11 -1
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/connection/http2.rb +33 -18
- data/lib/httpx/connection.rb +115 -55
- data/lib/httpx/errors.rb +3 -4
- data/lib/httpx/io/ssl.rb +6 -3
- data/lib/httpx/loggable.rb +13 -6
- data/lib/httpx/plugins/callbacks.rb +1 -0
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/expect.rb +1 -1
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/internal_telemetry.rb +21 -1
- data/lib/httpx/plugins/retries.rb +2 -2
- data/lib/httpx/plugins/stream.rb +42 -18
- data/lib/httpx/request/body.rb +9 -14
- data/lib/httpx/request.rb +37 -3
- data/lib/httpx/resolver/https.rb +4 -2
- data/lib/httpx/resolver/native.rb +111 -55
- data/lib/httpx/resolver/resolver.rb +18 -11
- data/lib/httpx/resolver/system.rb +3 -5
- data/lib/httpx/response.rb +9 -4
- data/lib/httpx/selector.rb +33 -23
- data/lib/httpx/session.rb +20 -49
- data/lib/httpx/timers.rb +16 -1
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/multipart/encoder.rb +2 -1
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -1
- data/sig/callbacks.rbs +2 -2
- data/sig/connection/http2.rbs +4 -0
- data/sig/connection.rbs +19 -5
- data/sig/errors.rbs +3 -3
- data/sig/loggable.rbs +2 -2
- data/sig/plugins/stream.rbs +3 -0
- data/sig/pool.rbs +2 -0
- data/sig/request/body.rbs +0 -8
- data/sig/request.rbs +12 -0
- data/sig/resolver/native.rbs +6 -1
- data/sig/response.rbs +8 -3
- data/sig/selector.rbs +1 -0
- data/sig/session.rbs +2 -0
- data/sig/timers.rbs +15 -4
- data/sig/transcoder/body.rbs +1 -3
- data/sig/transcoder/json.rbs +1 -1
- data/sig/transcoder/multipart.rbs +1 -1
- data/sig/transcoder/utils/body_reader.rbs +1 -1
- data/sig/transcoder/utils/deflater.rbs +1 -2
- metadata +11 -9
- data/lib/httpx/session2.rb +0 -23
- data/lib/httpx/transcoder/utils/inflater.rb +0 -21
- data/sig/transcoder/utils/inflater.rbs +0 -12
data/lib/httpx/connection.rb
CHANGED
@@ -41,15 +41,17 @@ module HTTPX
|
|
41
41
|
|
42
42
|
def_delegator :@write_buffer, :empty?
|
43
43
|
|
44
|
-
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
|
44
|
+
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
|
45
45
|
|
46
|
-
attr_writer :current_selector
|
46
|
+
attr_writer :current_selector
|
47
47
|
|
48
48
|
attr_accessor :current_session, :family
|
49
49
|
|
50
|
+
protected :sibling
|
51
|
+
|
50
52
|
def initialize(uri, options)
|
51
|
-
@current_session = @current_selector = @coalesced_connection = nil
|
52
|
-
@exhausted = @cloned = false
|
53
|
+
@current_session = @current_selector = @sibling = @coalesced_connection = nil
|
54
|
+
@exhausted = @cloned = @main_sibling = false
|
53
55
|
|
54
56
|
@options = Options.new(options)
|
55
57
|
@type = initialize_type(uri, @options)
|
@@ -70,9 +72,6 @@ module HTTPX
|
|
70
72
|
else
|
71
73
|
transition(:idle)
|
72
74
|
end
|
73
|
-
on(:activate) do
|
74
|
-
@current_session.select_connection(self, @current_selector)
|
75
|
-
end
|
76
75
|
on(:close) do
|
77
76
|
next if @exhausted # it'll reset
|
78
77
|
|
@@ -86,10 +85,13 @@ module HTTPX
|
|
86
85
|
on(:terminate) do
|
87
86
|
next if @exhausted # it'll reset
|
88
87
|
|
88
|
+
current_session = @current_session
|
89
|
+
current_selector = @current_selector
|
90
|
+
|
89
91
|
# may be called after ":close" above, so after the connection has been checked back in.
|
90
|
-
next unless
|
92
|
+
next unless current_session && current_selector
|
91
93
|
|
92
|
-
|
94
|
+
current_session.deselect_connection(self, current_selector)
|
93
95
|
end
|
94
96
|
|
95
97
|
on(:altsvc) do |alt_origin, origin, alt_params|
|
@@ -99,8 +101,6 @@ module HTTPX
|
|
99
101
|
@inflight = 0
|
100
102
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
101
103
|
|
102
|
-
@intervals = []
|
103
|
-
|
104
104
|
self.addresses = @options.addresses if @options.addresses
|
105
105
|
end
|
106
106
|
|
@@ -194,6 +194,12 @@ module HTTPX
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
+
def io_connected?
|
198
|
+
return @coalesced_connection.io_connected? if @coalesced_connection
|
199
|
+
|
200
|
+
@io && @io.state == :connected
|
201
|
+
end
|
202
|
+
|
197
203
|
def connecting?
|
198
204
|
@state == :idle
|
199
205
|
end
|
@@ -290,6 +296,7 @@ module HTTPX
|
|
290
296
|
@pending << request
|
291
297
|
transition(:active) if @state == :inactive
|
292
298
|
parser.ping
|
299
|
+
request.ping!
|
293
300
|
return
|
294
301
|
end
|
295
302
|
|
@@ -329,31 +336,54 @@ module HTTPX
|
|
329
336
|
end
|
330
337
|
|
331
338
|
def handle_socket_timeout(interval)
|
332
|
-
|
339
|
+
error = OperationTimeoutError.new(interval, "timed out while waiting on select")
|
340
|
+
error.set_backtrace(caller)
|
341
|
+
on_error(error)
|
342
|
+
end
|
333
343
|
|
334
|
-
|
335
|
-
|
344
|
+
def coalesced_connection=(connection)
|
345
|
+
@coalesced_connection = connection
|
336
346
|
|
337
|
-
|
338
|
-
|
347
|
+
close_sibling
|
348
|
+
connection.merge(self)
|
349
|
+
end
|
339
350
|
|
340
|
-
|
341
|
-
|
342
|
-
|
351
|
+
def sibling=(connection)
|
352
|
+
@sibling = connection
|
353
|
+
|
354
|
+
return unless connection
|
355
|
+
|
356
|
+
@main_sibling = connection.sibling.nil?
|
357
|
+
|
358
|
+
return unless @main_sibling
|
359
|
+
|
360
|
+
connection.sibling = self
|
343
361
|
end
|
344
362
|
|
345
|
-
|
363
|
+
def handle_connect_error(error)
|
364
|
+
@connect_error = error
|
346
365
|
|
347
|
-
|
348
|
-
|
366
|
+
return handle_error(error) unless @sibling && @sibling.connecting?
|
367
|
+
|
368
|
+
@sibling.merge(self)
|
369
|
+
|
370
|
+
force_reset(true)
|
349
371
|
end
|
350
372
|
|
351
373
|
def disconnect
|
374
|
+
return unless @current_session && @current_selector
|
375
|
+
|
352
376
|
emit(:close)
|
353
377
|
@current_session = nil
|
354
378
|
@current_selector = nil
|
355
379
|
end
|
356
380
|
|
381
|
+
private
|
382
|
+
|
383
|
+
def connect
|
384
|
+
transition(:open)
|
385
|
+
end
|
386
|
+
|
357
387
|
def consume
|
358
388
|
return unless @io
|
359
389
|
|
@@ -394,6 +424,8 @@ module HTTPX
|
|
394
424
|
siz = @io.read(@window_size, @read_buffer)
|
395
425
|
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
|
396
426
|
unless siz
|
427
|
+
@write_buffer.clear
|
428
|
+
|
397
429
|
ex = EOFError.new("descriptor closed")
|
398
430
|
ex.set_backtrace(caller)
|
399
431
|
on_error(ex)
|
@@ -448,6 +480,8 @@ module HTTPX
|
|
448
480
|
end
|
449
481
|
log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
|
450
482
|
unless siz
|
483
|
+
@write_buffer.clear
|
484
|
+
|
451
485
|
ex = EOFError.new("descriptor closed")
|
452
486
|
ex.set_backtrace(caller)
|
453
487
|
on_error(ex)
|
@@ -531,20 +565,22 @@ module HTTPX
|
|
531
565
|
@exhausted = true
|
532
566
|
current_session = @current_session
|
533
567
|
current_selector = @current_selector
|
534
|
-
|
535
|
-
|
568
|
+
begin
|
569
|
+
parser.close
|
570
|
+
@pending.concat(parser.pending)
|
571
|
+
ensure
|
572
|
+
@current_session = current_session
|
573
|
+
@current_selector = current_selector
|
574
|
+
end
|
575
|
+
|
536
576
|
case @state
|
537
577
|
when :closed
|
538
578
|
idling
|
539
579
|
@exhausted = false
|
540
|
-
@current_session = current_session
|
541
|
-
@current_selector = current_selector
|
542
580
|
when :closing
|
543
|
-
once(:
|
581
|
+
once(:closed) do
|
544
582
|
idling
|
545
583
|
@exhausted = false
|
546
|
-
@current_session = current_session
|
547
|
-
@current_selector = current_selector
|
548
584
|
end
|
549
585
|
end
|
550
586
|
end
|
@@ -577,9 +613,9 @@ module HTTPX
|
|
577
613
|
parser.on(:timeout) do |tout|
|
578
614
|
@timeout = tout
|
579
615
|
end
|
580
|
-
parser.on(:error) do |request,
|
581
|
-
case
|
582
|
-
when
|
616
|
+
parser.on(:error) do |request, error|
|
617
|
+
case error
|
618
|
+
when :http_1_1_required
|
583
619
|
current_session = @current_session
|
584
620
|
current_selector = @current_selector
|
585
621
|
parser.close
|
@@ -589,11 +625,16 @@ module HTTPX
|
|
589
625
|
other_connection.merge(self)
|
590
626
|
request.transition(:idle)
|
591
627
|
other_connection.send(request)
|
592
|
-
|
593
|
-
|
594
|
-
request
|
595
|
-
request.
|
628
|
+
next
|
629
|
+
when OperationTimeoutError
|
630
|
+
# request level timeouts should take precedence
|
631
|
+
next unless request.active_timeouts.empty?
|
596
632
|
end
|
633
|
+
|
634
|
+
@inflight -= 1
|
635
|
+
response = ErrorResponse.new(request, error)
|
636
|
+
request.response = response
|
637
|
+
request.emit(:response, response)
|
597
638
|
end
|
598
639
|
end
|
599
640
|
|
@@ -613,14 +654,16 @@ module HTTPX
|
|
613
654
|
# connect errors, exit gracefully
|
614
655
|
error = ConnectionError.new(e.message)
|
615
656
|
error.set_backtrace(e.backtrace)
|
616
|
-
|
657
|
+
handle_connect_error(error) if connecting?
|
617
658
|
@state = :closed
|
659
|
+
purge_after_closed
|
618
660
|
disconnect
|
619
661
|
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
620
662
|
# connect errors, exit gracefully
|
621
663
|
handle_error(e)
|
622
|
-
|
664
|
+
handle_connect_error(e) if connecting?
|
623
665
|
@state = :closed
|
666
|
+
purge_after_closed
|
624
667
|
disconnect
|
625
668
|
end
|
626
669
|
|
@@ -629,12 +672,12 @@ module HTTPX
|
|
629
672
|
when :idle
|
630
673
|
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
631
674
|
|
632
|
-
@connected_at = nil
|
675
|
+
@connected_at = @response_received_at = nil
|
633
676
|
when :open
|
634
677
|
return if @state == :closed
|
635
678
|
|
636
679
|
@io.connect
|
637
|
-
|
680
|
+
close_sibling if @io.state == :connected
|
638
681
|
|
639
682
|
return unless @io.connected?
|
640
683
|
|
@@ -667,6 +710,7 @@ module HTTPX
|
|
667
710
|
|
668
711
|
purge_after_closed
|
669
712
|
disconnect if @pending.empty?
|
713
|
+
|
670
714
|
when :already_open
|
671
715
|
nextstate = :open
|
672
716
|
# the first check for given io readiness must still use a timeout.
|
@@ -677,11 +721,29 @@ module HTTPX
|
|
677
721
|
return unless @state == :inactive
|
678
722
|
|
679
723
|
nextstate = :open
|
680
|
-
|
724
|
+
|
725
|
+
# activate
|
726
|
+
@current_session.select_connection(self, @current_selector)
|
681
727
|
end
|
682
728
|
@state = nextstate
|
683
729
|
end
|
684
730
|
|
731
|
+
def close_sibling
|
732
|
+
return unless @sibling
|
733
|
+
|
734
|
+
if @sibling.io_connected?
|
735
|
+
reset
|
736
|
+
# TODO: transition connection to closed
|
737
|
+
end
|
738
|
+
|
739
|
+
unless @sibling.state == :closed
|
740
|
+
merge(@sibling) unless @main_sibling
|
741
|
+
@sibling.force_reset(true)
|
742
|
+
end
|
743
|
+
|
744
|
+
@sibling = nil
|
745
|
+
end
|
746
|
+
|
685
747
|
def purge_after_closed
|
686
748
|
@io.close if @io
|
687
749
|
@read_buffer.clear
|
@@ -754,7 +816,7 @@ module HTTPX
|
|
754
816
|
end
|
755
817
|
|
756
818
|
def on_error(error, request = nil)
|
757
|
-
if error.
|
819
|
+
if error.is_a?(OperationTimeoutError)
|
758
820
|
|
759
821
|
# inactive connections do not contribute to the select loop, therefore
|
760
822
|
# they should not fail due to such errors.
|
@@ -783,6 +845,7 @@ module HTTPX
|
|
783
845
|
|
784
846
|
return unless request
|
785
847
|
|
848
|
+
@inflight -= 1
|
786
849
|
response = ErrorResponse.new(request, error)
|
787
850
|
request.response = response
|
788
851
|
request.emit(:response, response)
|
@@ -799,7 +862,7 @@ module HTTPX
|
|
799
862
|
|
800
863
|
return if read_timeout.nil? || read_timeout.infinite?
|
801
864
|
|
802
|
-
set_request_timeout(request, read_timeout, :done, :response) do
|
865
|
+
set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
|
803
866
|
read_timeout_callback(request, read_timeout)
|
804
867
|
end
|
805
868
|
end
|
@@ -809,7 +872,7 @@ module HTTPX
|
|
809
872
|
|
810
873
|
return if write_timeout.nil? || write_timeout.infinite?
|
811
874
|
|
812
|
-
set_request_timeout(request, write_timeout, :headers, %i[done response]) do
|
875
|
+
set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
|
813
876
|
write_timeout_callback(request, write_timeout)
|
814
877
|
end
|
815
878
|
end
|
@@ -819,7 +882,7 @@ module HTTPX
|
|
819
882
|
|
820
883
|
return if request_timeout.nil? || request_timeout.infinite?
|
821
884
|
|
822
|
-
set_request_timeout(request, request_timeout, :headers, :complete) do
|
885
|
+
set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
|
823
886
|
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
824
887
|
end
|
825
888
|
end
|
@@ -844,21 +907,18 @@ module HTTPX
|
|
844
907
|
on_error(error, request)
|
845
908
|
end
|
846
909
|
|
847
|
-
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
848
|
-
request.
|
849
|
-
|
910
|
+
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
911
|
+
request.set_timeout_callback(start_event) do
|
912
|
+
timer = @current_selector.after(timeout, callback)
|
913
|
+
request.active_timeouts << label
|
850
914
|
|
851
915
|
Array(finish_events).each do |event|
|
852
916
|
# clean up request timeouts if the connection errors out
|
853
|
-
request.
|
854
|
-
|
855
|
-
|
856
|
-
@intervals.delete(interval) if interval.no_callbacks?
|
857
|
-
end
|
917
|
+
request.set_timeout_callback(event) do
|
918
|
+
timer.cancel
|
919
|
+
request.active_timeouts.delete(label)
|
858
920
|
end
|
859
921
|
end
|
860
|
-
|
861
|
-
@intervals << interval
|
862
922
|
end
|
863
923
|
end
|
864
924
|
|
data/lib/httpx/errors.rb
CHANGED
@@ -77,6 +77,9 @@ module HTTPX
|
|
77
77
|
# Error raised when there was a timeout while resolving a domain to an IP.
|
78
78
|
class ResolveTimeoutError < TimeoutError; end
|
79
79
|
|
80
|
+
# Error raise when there was a timeout waiting for readiness of the socket the request is related to.
|
81
|
+
class OperationTimeoutError < TimeoutError; end
|
82
|
+
|
80
83
|
# Error raised when there was an error while resolving a domain to an IP.
|
81
84
|
class ResolveError < Error; end
|
82
85
|
|
@@ -112,8 +115,4 @@ module HTTPX
|
|
112
115
|
@response.status
|
113
116
|
end
|
114
117
|
end
|
115
|
-
|
116
|
-
# error raised when a request was sent a server which can't reproduce a response, and
|
117
|
-
# has therefore returned an HTTP response using the 421 status code.
|
118
|
-
class MisdirectedRequestError < HTTPError; end
|
119
118
|
end
|
data/lib/httpx/io/ssl.rb
CHANGED
@@ -92,9 +92,12 @@ module HTTPX
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def connect
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
return if @state == :negotiated
|
96
|
+
|
97
|
+
unless @state == :connected
|
98
|
+
super
|
99
|
+
return unless @state == :connected
|
100
|
+
end
|
98
101
|
|
99
102
|
unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
100
103
|
if (hostname_is_ip = (@ip == @sni_hostname))
|
data/lib/httpx/loggable.rb
CHANGED
@@ -15,20 +15,27 @@ module HTTPX
|
|
15
15
|
|
16
16
|
USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
|
17
17
|
|
18
|
-
def log(level: @options.debug_level, color: nil, &msg)
|
19
|
-
return unless
|
18
|
+
def log(level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug, &msg)
|
19
|
+
return unless debug_level >= level
|
20
20
|
|
21
|
-
debug_stream =
|
21
|
+
debug_stream = debug || ($stderr if USE_DEBUG_LOG)
|
22
22
|
|
23
23
|
return unless debug_stream
|
24
24
|
|
25
|
-
|
25
|
+
klass = self.class
|
26
|
+
|
27
|
+
until (class_name = klass.name)
|
28
|
+
klass = klass.superclass
|
29
|
+
end
|
30
|
+
|
31
|
+
message = +"(pid:#{Process.pid} tid:#{Thread.current.object_id}, self:#{class_name}##{object_id}) "
|
32
|
+
message << msg.call << "\n"
|
26
33
|
message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
|
27
34
|
debug_stream << message
|
28
35
|
end
|
29
36
|
|
30
|
-
def log_exception(ex, level: @options.debug_level, color: nil)
|
31
|
-
log(level: level, color: color) { ex.full_message }
|
37
|
+
def log_exception(ex, level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug)
|
38
|
+
log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
data/lib/httpx/plugins/expect.rb
CHANGED
@@ -84,7 +84,7 @@ module HTTPX
|
|
84
84
|
|
85
85
|
return if expect_timeout.nil? || expect_timeout.infinite?
|
86
86
|
|
87
|
-
set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
|
87
|
+
set_request_timeout(:expect_timeout, request, expect_timeout, :expect, %i[body response]) do
|
88
88
|
# expect timeout expired
|
89
89
|
if request.state == :expect && !request.expects?
|
90
90
|
Expect.no_expect_store << request.origin
|
@@ -13,6 +13,12 @@ module HTTPX
|
|
13
13
|
# by the end user in $http_init_time, different diff metrics can be shown. The "point of time" is calculated
|
14
14
|
# using the monotonic clock.
|
15
15
|
module InternalTelemetry
|
16
|
+
DEBUG_LEVEL = 3
|
17
|
+
|
18
|
+
def self.extra_options(options)
|
19
|
+
options.merge(debug_level: 3)
|
20
|
+
end
|
21
|
+
|
16
22
|
module TrackTimeMethods
|
17
23
|
private
|
18
24
|
|
@@ -28,7 +34,19 @@ module HTTPX
|
|
28
34
|
after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
29
35
|
# $http_init_time = after_time
|
30
36
|
elapsed = after_time - prev_time
|
31
|
-
|
37
|
+
# klass = self.class
|
38
|
+
|
39
|
+
# until (class_name = klass.name)
|
40
|
+
# klass = klass.superclass
|
41
|
+
# end
|
42
|
+
log(
|
43
|
+
level: DEBUG_LEVEL,
|
44
|
+
color: :red,
|
45
|
+
debug_level: @options ? @options.debug_level : DEBUG_LEVEL,
|
46
|
+
debug: nil
|
47
|
+
) do
|
48
|
+
"[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m"
|
49
|
+
end
|
32
50
|
end
|
33
51
|
end
|
34
52
|
|
@@ -88,6 +106,7 @@ module HTTPX
|
|
88
106
|
|
89
107
|
module RequestMethods
|
90
108
|
def self.included(klass)
|
109
|
+
klass.prepend Loggable
|
91
110
|
klass.prepend TrackTimeMethods
|
92
111
|
super
|
93
112
|
end
|
@@ -114,6 +133,7 @@ module HTTPX
|
|
114
133
|
|
115
134
|
module PoolMethods
|
116
135
|
def self.included(klass)
|
136
|
+
klass.prepend Loggable
|
117
137
|
klass.prepend TrackTimeMethods
|
118
138
|
super
|
119
139
|
end
|
@@ -110,7 +110,7 @@ module HTTPX
|
|
110
110
|
)
|
111
111
|
__try_partial_retry(request, response)
|
112
112
|
log { "failed to get response, #{request.retries} tries to go..." }
|
113
|
-
request.retries -= 1
|
113
|
+
request.retries -= 1 unless request.ping? # do not exhaust retries on connection liveness probes
|
114
114
|
request.transition(:idle)
|
115
115
|
|
116
116
|
retry_after = options.retry_after
|
@@ -167,7 +167,7 @@ module HTTPX
|
|
167
167
|
unless response.headers.key?("accept-ranges") &&
|
168
168
|
response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
|
169
169
|
(original_body = response.body)
|
170
|
-
response.
|
170
|
+
response.body.close
|
171
171
|
return
|
172
172
|
end
|
173
173
|
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -4,27 +4,39 @@ module HTTPX
|
|
4
4
|
class StreamResponse
|
5
5
|
def initialize(request, session)
|
6
6
|
@request = request
|
7
|
+
@options = @request.options
|
7
8
|
@session = session
|
8
|
-
@
|
9
|
+
@response_enum = nil
|
10
|
+
@buffered_chunks = []
|
9
11
|
end
|
10
12
|
|
11
13
|
def each(&block)
|
12
14
|
return enum_for(__method__) unless block
|
13
15
|
|
16
|
+
if (response_enum = @response_enum)
|
17
|
+
@response_enum = nil
|
18
|
+
# streaming already started, let's finish it
|
19
|
+
|
20
|
+
while (chunk = @buffered_chunks.shift)
|
21
|
+
block.call(chunk)
|
22
|
+
end
|
23
|
+
|
24
|
+
# consume enum til the end
|
25
|
+
begin
|
26
|
+
while (chunk = response_enum.next)
|
27
|
+
block.call(chunk)
|
28
|
+
end
|
29
|
+
rescue StopIteration
|
30
|
+
return
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
14
34
|
@request.stream = self
|
15
35
|
|
16
36
|
begin
|
17
37
|
@on_chunk = block
|
18
38
|
|
19
|
-
|
20
|
-
# if we've already started collecting the payload, yield it first
|
21
|
-
# before proceeding.
|
22
|
-
body = @request.response.body
|
23
|
-
|
24
|
-
body.each do |chunk|
|
25
|
-
on_chunk(chunk)
|
26
|
-
end
|
27
|
-
end
|
39
|
+
response = @session.request(@request)
|
28
40
|
|
29
41
|
response.raise_for_status
|
30
42
|
ensure
|
@@ -64,27 +76,39 @@ module HTTPX
|
|
64
76
|
# :nocov:
|
65
77
|
|
66
78
|
def to_s
|
67
|
-
response
|
79
|
+
if @request.response
|
80
|
+
@request.response.to_s
|
81
|
+
else
|
82
|
+
@buffered_chunks.join
|
83
|
+
end
|
68
84
|
end
|
69
85
|
|
70
86
|
private
|
71
87
|
|
72
88
|
def response
|
73
|
-
return @response if @response
|
74
|
-
|
75
89
|
@request.response || begin
|
76
|
-
|
90
|
+
response_enum = each
|
91
|
+
while (chunk = response_enum.next)
|
92
|
+
@buffered_chunks << chunk
|
93
|
+
break if @request.response
|
94
|
+
end
|
95
|
+
@response_enum = response_enum
|
96
|
+
@request.response
|
77
97
|
end
|
78
98
|
end
|
79
99
|
|
80
|
-
def respond_to_missing?(meth,
|
81
|
-
response.
|
100
|
+
def respond_to_missing?(meth, include_private)
|
101
|
+
if (response = @request.response)
|
102
|
+
response.respond_to_missing?(meth, include_private)
|
103
|
+
else
|
104
|
+
@options.response_class.method_defined?(meth) || (include_private && @options.response_class.private_method_defined?(meth))
|
105
|
+
end || super
|
82
106
|
end
|
83
107
|
|
84
|
-
def method_missing(meth, *args, &block)
|
108
|
+
def method_missing(meth, *args, **kwargs, &block)
|
85
109
|
return super unless response.respond_to?(meth)
|
86
110
|
|
87
|
-
response.__send__(meth, *args, &block)
|
111
|
+
response.__send__(meth, *args, **kwargs, &block)
|
88
112
|
end
|
89
113
|
end
|
90
114
|
|
data/lib/httpx/request/body.rb
CHANGED
@@ -52,7 +52,11 @@ module HTTPX
|
|
52
52
|
|
53
53
|
body = stream(@body)
|
54
54
|
if body.respond_to?(:read)
|
55
|
-
|
55
|
+
while (chunk = body.read(16_384))
|
56
|
+
block.call(chunk)
|
57
|
+
end
|
58
|
+
# TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
|
59
|
+
# ::IO.copy_stream(body, ProcIO.new(block))
|
56
60
|
elsif body.respond_to?(:each)
|
57
61
|
body.each(&block)
|
58
62
|
else
|
@@ -60,6 +64,10 @@ module HTTPX
|
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
67
|
+
def close
|
68
|
+
@body.close if @body.respond_to?(:close)
|
69
|
+
end
|
70
|
+
|
63
71
|
# if the +@body+ is rewindable, it rewinnds it.
|
64
72
|
def rewind
|
65
73
|
return if empty?
|
@@ -142,17 +150,4 @@ module HTTPX
|
|
142
150
|
end
|
143
151
|
end
|
144
152
|
end
|
145
|
-
|
146
|
-
# Wrapper yielder which can be used with functions which expect an IO writer.
|
147
|
-
class ProcIO
|
148
|
-
def initialize(block)
|
149
|
-
@block = block
|
150
|
-
end
|
151
|
-
|
152
|
-
# Implementation the IO write protocol, which yield the given chunk to +@block+.
|
153
|
-
def write(data)
|
154
|
-
@block.call(data.dup)
|
155
|
-
data.bytesize
|
156
|
-
end
|
157
|
-
end
|
158
153
|
end
|