httpx 1.4.0 → 1.4.2
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/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +7 -1
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/connection/http2.rb +2 -2
- data/lib/httpx/connection.rb +106 -51
- data/lib/httpx/errors.rb +3 -0
- data/lib/httpx/loggable.rb +8 -1
- 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/request/body.rb +9 -14
- data/lib/httpx/request.rb +20 -0
- 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/selector.rb +33 -23
- data/lib/httpx/session.rb +17 -43
- data/lib/httpx/timers.rb +16 -1
- data/lib/httpx/transcoder/body.rb +15 -31
- 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.rbs +19 -5
- data/sig/errors.rbs +3 -0
- data/sig/request/body.rbs +0 -8
- data/sig/request.rbs +3 -0
- data/sig/resolver/native.rbs +6 -1
- 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/utils/body_reader.rbs +1 -1
- data/sig/transcoder/utils/deflater.rbs +1 -1
- metadata +7 -3
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
|
@@ -329,31 +335,54 @@ module HTTPX
|
|
329
335
|
end
|
330
336
|
|
331
337
|
def handle_socket_timeout(interval)
|
332
|
-
|
338
|
+
error = OperationTimeoutError.new(interval, "timed out while waiting on select")
|
339
|
+
error.set_backtrace(caller)
|
340
|
+
on_error(error)
|
341
|
+
end
|
333
342
|
|
334
|
-
|
335
|
-
|
343
|
+
def coalesced_connection=(connection)
|
344
|
+
@coalesced_connection = connection
|
336
345
|
|
337
|
-
|
338
|
-
|
346
|
+
close_sibling
|
347
|
+
connection.merge(self)
|
348
|
+
end
|
339
349
|
|
340
|
-
|
341
|
-
|
342
|
-
|
350
|
+
def sibling=(connection)
|
351
|
+
@sibling = connection
|
352
|
+
|
353
|
+
return unless connection
|
354
|
+
|
355
|
+
@main_sibling = connection.sibling.nil?
|
356
|
+
|
357
|
+
return unless @main_sibling
|
358
|
+
|
359
|
+
connection.sibling = self
|
343
360
|
end
|
344
361
|
|
345
|
-
|
362
|
+
def handle_connect_error(error)
|
363
|
+
@connect_error = error
|
346
364
|
|
347
|
-
|
348
|
-
|
365
|
+
return handle_error(error) unless @sibling && @sibling.connecting?
|
366
|
+
|
367
|
+
@sibling.merge(self)
|
368
|
+
|
369
|
+
force_reset(true)
|
349
370
|
end
|
350
371
|
|
351
372
|
def disconnect
|
373
|
+
return unless @current_session && @current_selector
|
374
|
+
|
352
375
|
emit(:close)
|
353
376
|
@current_session = nil
|
354
377
|
@current_selector = nil
|
355
378
|
end
|
356
379
|
|
380
|
+
private
|
381
|
+
|
382
|
+
def connect
|
383
|
+
transition(:open)
|
384
|
+
end
|
385
|
+
|
357
386
|
def consume
|
358
387
|
return unless @io
|
359
388
|
|
@@ -448,6 +477,8 @@ module HTTPX
|
|
448
477
|
end
|
449
478
|
log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
|
450
479
|
unless siz
|
480
|
+
@write_buffer.clear
|
481
|
+
|
451
482
|
ex = EOFError.new("descriptor closed")
|
452
483
|
ex.set_backtrace(caller)
|
453
484
|
on_error(ex)
|
@@ -531,20 +562,22 @@ module HTTPX
|
|
531
562
|
@exhausted = true
|
532
563
|
current_session = @current_session
|
533
564
|
current_selector = @current_selector
|
534
|
-
|
535
|
-
|
565
|
+
begin
|
566
|
+
parser.close
|
567
|
+
@pending.concat(parser.pending)
|
568
|
+
ensure
|
569
|
+
@current_session = current_session
|
570
|
+
@current_selector = current_selector
|
571
|
+
end
|
572
|
+
|
536
573
|
case @state
|
537
574
|
when :closed
|
538
575
|
idling
|
539
576
|
@exhausted = false
|
540
|
-
@current_session = current_session
|
541
|
-
@current_selector = current_selector
|
542
577
|
when :closing
|
543
|
-
once(:
|
578
|
+
once(:closed) do
|
544
579
|
idling
|
545
580
|
@exhausted = false
|
546
|
-
@current_session = current_session
|
547
|
-
@current_selector = current_selector
|
548
581
|
end
|
549
582
|
end
|
550
583
|
end
|
@@ -589,11 +622,15 @@ module HTTPX
|
|
589
622
|
other_connection.merge(self)
|
590
623
|
request.transition(:idle)
|
591
624
|
other_connection.send(request)
|
592
|
-
|
593
|
-
|
594
|
-
request
|
595
|
-
request.
|
625
|
+
next
|
626
|
+
when OperationTimeoutError
|
627
|
+
# request level timeouts should take precedence
|
628
|
+
next unless request.active_timeouts.empty?
|
596
629
|
end
|
630
|
+
|
631
|
+
response = ErrorResponse.new(request, ex)
|
632
|
+
request.response = response
|
633
|
+
request.emit(:response, response)
|
597
634
|
end
|
598
635
|
end
|
599
636
|
|
@@ -613,14 +650,16 @@ module HTTPX
|
|
613
650
|
# connect errors, exit gracefully
|
614
651
|
error = ConnectionError.new(e.message)
|
615
652
|
error.set_backtrace(e.backtrace)
|
616
|
-
|
653
|
+
handle_connect_error(error) if connecting?
|
617
654
|
@state = :closed
|
655
|
+
purge_after_closed
|
618
656
|
disconnect
|
619
657
|
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
620
658
|
# connect errors, exit gracefully
|
621
659
|
handle_error(e)
|
622
|
-
|
660
|
+
handle_connect_error(e) if connecting?
|
623
661
|
@state = :closed
|
662
|
+
purge_after_closed
|
624
663
|
disconnect
|
625
664
|
end
|
626
665
|
|
@@ -634,7 +673,7 @@ module HTTPX
|
|
634
673
|
return if @state == :closed
|
635
674
|
|
636
675
|
@io.connect
|
637
|
-
|
676
|
+
close_sibling if @io.state == :connected
|
638
677
|
|
639
678
|
return unless @io.connected?
|
640
679
|
|
@@ -667,6 +706,7 @@ module HTTPX
|
|
667
706
|
|
668
707
|
purge_after_closed
|
669
708
|
disconnect if @pending.empty?
|
709
|
+
|
670
710
|
when :already_open
|
671
711
|
nextstate = :open
|
672
712
|
# the first check for given io readiness must still use a timeout.
|
@@ -677,11 +717,29 @@ module HTTPX
|
|
677
717
|
return unless @state == :inactive
|
678
718
|
|
679
719
|
nextstate = :open
|
680
|
-
|
720
|
+
|
721
|
+
# activate
|
722
|
+
@current_session.select_connection(self, @current_selector)
|
681
723
|
end
|
682
724
|
@state = nextstate
|
683
725
|
end
|
684
726
|
|
727
|
+
def close_sibling
|
728
|
+
return unless @sibling
|
729
|
+
|
730
|
+
if @sibling.io_connected?
|
731
|
+
reset
|
732
|
+
# TODO: transition connection to closed
|
733
|
+
end
|
734
|
+
|
735
|
+
unless @sibling.state == :closed
|
736
|
+
merge(@sibling) unless @main_sibling
|
737
|
+
@sibling.force_reset(true)
|
738
|
+
end
|
739
|
+
|
740
|
+
@sibling = nil
|
741
|
+
end
|
742
|
+
|
685
743
|
def purge_after_closed
|
686
744
|
@io.close if @io
|
687
745
|
@read_buffer.clear
|
@@ -754,7 +812,7 @@ module HTTPX
|
|
754
812
|
end
|
755
813
|
|
756
814
|
def on_error(error, request = nil)
|
757
|
-
if error.
|
815
|
+
if error.is_a?(OperationTimeoutError)
|
758
816
|
|
759
817
|
# inactive connections do not contribute to the select loop, therefore
|
760
818
|
# they should not fail due to such errors.
|
@@ -799,7 +857,7 @@ module HTTPX
|
|
799
857
|
|
800
858
|
return if read_timeout.nil? || read_timeout.infinite?
|
801
859
|
|
802
|
-
set_request_timeout(request, read_timeout, :done, :response) do
|
860
|
+
set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
|
803
861
|
read_timeout_callback(request, read_timeout)
|
804
862
|
end
|
805
863
|
end
|
@@ -809,7 +867,7 @@ module HTTPX
|
|
809
867
|
|
810
868
|
return if write_timeout.nil? || write_timeout.infinite?
|
811
869
|
|
812
|
-
set_request_timeout(request, write_timeout, :headers, %i[done response]) do
|
870
|
+
set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
|
813
871
|
write_timeout_callback(request, write_timeout)
|
814
872
|
end
|
815
873
|
end
|
@@ -819,7 +877,7 @@ module HTTPX
|
|
819
877
|
|
820
878
|
return if request_timeout.nil? || request_timeout.infinite?
|
821
879
|
|
822
|
-
set_request_timeout(request, request_timeout, :headers, :complete) do
|
880
|
+
set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
|
823
881
|
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
824
882
|
end
|
825
883
|
end
|
@@ -844,21 +902,18 @@ module HTTPX
|
|
844
902
|
on_error(error, request)
|
845
903
|
end
|
846
904
|
|
847
|
-
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
848
|
-
request.
|
849
|
-
|
905
|
+
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
906
|
+
request.set_timeout_callback(start_event) do
|
907
|
+
timer = @current_selector.after(timeout, callback)
|
908
|
+
request.active_timeouts << label
|
850
909
|
|
851
910
|
Array(finish_events).each do |event|
|
852
911
|
# clean up request timeouts if the connection errors out
|
853
|
-
request.
|
854
|
-
|
855
|
-
|
856
|
-
@intervals.delete(interval) if interval.no_callbacks?
|
857
|
-
end
|
912
|
+
request.set_timeout_callback(event) do
|
913
|
+
timer.cancel
|
914
|
+
request.active_timeouts.delete(label)
|
858
915
|
end
|
859
916
|
end
|
860
|
-
|
861
|
-
@intervals << interval
|
862
917
|
end
|
863
918
|
end
|
864
919
|
|
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
|
|
data/lib/httpx/loggable.rb
CHANGED
@@ -22,7 +22,14 @@ module HTTPX
|
|
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
|
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
|
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
|
data/lib/httpx/request.rb
CHANGED
@@ -11,6 +11,8 @@ module HTTPX
|
|
11
11
|
include Callbacks
|
12
12
|
using URIExtensions
|
13
13
|
|
14
|
+
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
15
|
+
|
14
16
|
# default value used for "user-agent" header, when not overridden.
|
15
17
|
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
16
18
|
|
@@ -43,6 +45,8 @@ module HTTPX
|
|
43
45
|
|
44
46
|
attr_writer :persistent
|
45
47
|
|
48
|
+
attr_reader :active_timeouts
|
49
|
+
|
46
50
|
# will be +true+ when request body has been completely flushed.
|
47
51
|
def_delegator :@body, :empty?
|
48
52
|
|
@@ -92,10 +96,13 @@ module HTTPX
|
|
92
96
|
@uri = origin.merge("#{base_path}#{@uri}")
|
93
97
|
end
|
94
98
|
|
99
|
+
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
100
|
+
|
95
101
|
@state = :idle
|
96
102
|
@response = nil
|
97
103
|
@peer_address = nil
|
98
104
|
@persistent = @options.persistent
|
105
|
+
@active_timeouts = []
|
99
106
|
end
|
100
107
|
|
101
108
|
# the read timeout defined for this requet.
|
@@ -241,8 +248,10 @@ module HTTPX
|
|
241
248
|
@body.rewind
|
242
249
|
@response = nil
|
243
250
|
@drainer = nil
|
251
|
+
@active_timeouts.clear
|
244
252
|
when :headers
|
245
253
|
return unless @state == :idle
|
254
|
+
|
246
255
|
when :body
|
247
256
|
return unless @state == :headers ||
|
248
257
|
@state == :expect
|
@@ -263,6 +272,8 @@ module HTTPX
|
|
263
272
|
return unless @state == :body
|
264
273
|
when :done
|
265
274
|
return if @state == :expect
|
275
|
+
|
276
|
+
@body.close
|
266
277
|
end
|
267
278
|
@state = nextstate
|
268
279
|
emit(@state, self)
|
@@ -273,6 +284,15 @@ module HTTPX
|
|
273
284
|
def expects?
|
274
285
|
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
275
286
|
end
|
287
|
+
|
288
|
+
def set_timeout_callback(event, &callback)
|
289
|
+
clb = once(event, &callback)
|
290
|
+
|
291
|
+
# reset timeout callbacks when requests get rerouted to a different connection
|
292
|
+
once(:idle) do
|
293
|
+
callbacks(event).delete(clb)
|
294
|
+
end
|
295
|
+
end
|
276
296
|
end
|
277
297
|
end
|
278
298
|
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -82,7 +82,9 @@ module HTTPX
|
|
82
82
|
|
83
83
|
if hostname.nil?
|
84
84
|
hostname = connection.peer.host
|
85
|
-
log
|
85
|
+
log do
|
86
|
+
"resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
87
|
+
end if connection.peer.non_ascii_hostname
|
86
88
|
|
87
89
|
hostname = @resolver.generate_candidates(hostname).each do |name|
|
88
90
|
@queries[name.to_s] = connection
|
@@ -90,7 +92,7 @@ module HTTPX
|
|
90
92
|
else
|
91
93
|
@queries[hostname] = connection
|
92
94
|
end
|
93
|
-
log { "resolver
|
95
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
94
96
|
|
95
97
|
begin
|
96
98
|
request = build_request(hostname)
|