httpx 1.3.4 → 1.4.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_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +18 -6
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +12 -8
- data/lib/httpx/connection.rb +192 -22
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +25 -26
- data/lib/httpx/request.rb +7 -1
- data/lib/httpx/resolver/https.rb +15 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +66 -25
- data/lib/httpx/resolver/resolver.rb +59 -15
- data/lib/httpx/resolver/system.rb +31 -15
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +160 -95
- data/lib/httpx/session.rb +273 -140
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +29 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -9
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +2 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/buffer.rbs +1 -1
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +47 -18
- data/sig/transcoder/body.rbs +2 -4
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +3 -3
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/connection.rb
CHANGED
@@ -41,15 +41,18 @@ 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 :
|
46
|
+
attr_writer :current_selector
|
47
47
|
|
48
|
-
attr_accessor :family
|
48
|
+
attr_accessor :current_session, :family
|
49
|
+
|
50
|
+
protected :sibling
|
49
51
|
|
50
52
|
def initialize(uri, options)
|
51
|
-
@
|
52
|
-
@
|
53
|
+
@current_session = @current_selector = @sibling = @coalesced_connection = nil
|
54
|
+
@exhausted = @cloned = @main_sibling = false
|
55
|
+
|
53
56
|
@options = Options.new(options)
|
54
57
|
@type = initialize_type(uri, @options)
|
55
58
|
@origins = [uri.origin]
|
@@ -58,6 +61,7 @@ module HTTPX
|
|
58
61
|
@read_buffer = Buffer.new(@options.buffer_size)
|
59
62
|
@write_buffer = Buffer.new(@options.buffer_size)
|
60
63
|
@pending = []
|
64
|
+
|
61
65
|
on(:error, &method(:on_error))
|
62
66
|
if @options.io
|
63
67
|
# if there's an already open IO, get its
|
@@ -68,6 +72,31 @@ module HTTPX
|
|
68
72
|
else
|
69
73
|
transition(:idle)
|
70
74
|
end
|
75
|
+
on(:close) do
|
76
|
+
next if @exhausted # it'll reset
|
77
|
+
|
78
|
+
# may be called after ":close" above, so after the connection has been checked back in.
|
79
|
+
# next unless @current_session
|
80
|
+
|
81
|
+
next unless @current_session
|
82
|
+
|
83
|
+
@current_session.deselect_connection(self, @current_selector, @cloned)
|
84
|
+
end
|
85
|
+
on(:terminate) do
|
86
|
+
next if @exhausted # it'll reset
|
87
|
+
|
88
|
+
current_session = @current_session
|
89
|
+
current_selector = @current_selector
|
90
|
+
|
91
|
+
# may be called after ":close" above, so after the connection has been checked back in.
|
92
|
+
next unless current_session && current_selector
|
93
|
+
|
94
|
+
current_session.deselect_connection(self, current_selector)
|
95
|
+
end
|
96
|
+
|
97
|
+
on(:altsvc) do |alt_origin, origin, alt_params|
|
98
|
+
build_altsvc_connection(alt_origin, origin, alt_params)
|
99
|
+
end
|
71
100
|
|
72
101
|
@inflight = 0
|
73
102
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
@@ -77,6 +106,10 @@ module HTTPX
|
|
77
106
|
self.addresses = @options.addresses if @options.addresses
|
78
107
|
end
|
79
108
|
|
109
|
+
def peer
|
110
|
+
@origin
|
111
|
+
end
|
112
|
+
|
80
113
|
# this is a semi-private method, to be used by the resolver
|
81
114
|
# to initiate the io object.
|
82
115
|
def addresses=(addrs)
|
@@ -163,12 +196,23 @@ module HTTPX
|
|
163
196
|
end
|
164
197
|
end
|
165
198
|
|
199
|
+
def io_connected?
|
200
|
+
return @coalesced_connection.io_connected? if @coalesced_connection
|
201
|
+
|
202
|
+
@io && @io.state == :connected
|
203
|
+
end
|
204
|
+
|
166
205
|
def connecting?
|
167
206
|
@state == :idle
|
168
207
|
end
|
169
208
|
|
170
209
|
def inflight?
|
171
|
-
@parser &&
|
210
|
+
@parser && (
|
211
|
+
# parser may be dealing with other requests (possibly started from a different fiber)
|
212
|
+
!@parser.empty? ||
|
213
|
+
# connection may be doing connection termination handshake
|
214
|
+
!@write_buffer.empty?
|
215
|
+
)
|
172
216
|
end
|
173
217
|
|
174
218
|
def interests
|
@@ -184,6 +228,9 @@ module HTTPX
|
|
184
228
|
|
185
229
|
return @parser.interests if @parser
|
186
230
|
|
231
|
+
nil
|
232
|
+
rescue StandardError => e
|
233
|
+
emit(:error, e)
|
187
234
|
nil
|
188
235
|
end
|
189
236
|
|
@@ -205,6 +252,9 @@ module HTTPX
|
|
205
252
|
consume
|
206
253
|
end
|
207
254
|
nil
|
255
|
+
rescue StandardError => e
|
256
|
+
emit(:error, e)
|
257
|
+
raise e
|
208
258
|
end
|
209
259
|
|
210
260
|
def close
|
@@ -221,8 +271,9 @@ module HTTPX
|
|
221
271
|
|
222
272
|
# bypasses the state machine to force closing of connections still connecting.
|
223
273
|
# **only** used for Happy Eyeballs v2.
|
224
|
-
def force_reset
|
274
|
+
def force_reset(cloned = false)
|
225
275
|
@state = :closing
|
276
|
+
@cloned = cloned
|
226
277
|
transition(:closed)
|
227
278
|
end
|
228
279
|
|
@@ -235,6 +286,8 @@ module HTTPX
|
|
235
286
|
end
|
236
287
|
|
237
288
|
def send(request)
|
289
|
+
return @coalesced_connection.send(request) if @coalesced_connection
|
290
|
+
|
238
291
|
if @parser && !@write_buffer.full?
|
239
292
|
if @response_received_at && @keep_alive_timeout &&
|
240
293
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
@@ -255,6 +308,8 @@ module HTTPX
|
|
255
308
|
end
|
256
309
|
|
257
310
|
def timeout
|
311
|
+
return if @state == :closed || @state == :inactive
|
312
|
+
|
258
313
|
return @timeout if @timeout
|
259
314
|
|
260
315
|
return @options.timeout[:connect_timeout] if @state == :idle
|
@@ -295,12 +350,47 @@ module HTTPX
|
|
295
350
|
on_error(error)
|
296
351
|
end
|
297
352
|
|
353
|
+
def coalesced_connection=(connection)
|
354
|
+
@coalesced_connection = connection
|
355
|
+
|
356
|
+
close_sibling
|
357
|
+
connection.merge(self)
|
358
|
+
end
|
359
|
+
|
360
|
+
def sibling=(connection)
|
361
|
+
@sibling = connection
|
362
|
+
|
363
|
+
return unless connection
|
364
|
+
|
365
|
+
@main_sibling = connection.sibling.nil?
|
366
|
+
|
367
|
+
return unless @main_sibling
|
368
|
+
|
369
|
+
connection.sibling = self
|
370
|
+
end
|
371
|
+
|
372
|
+
def handle_connect_error(error)
|
373
|
+
@connect_error = error
|
374
|
+
|
375
|
+
return handle_error(error) unless @sibling && @sibling.connecting?
|
376
|
+
|
377
|
+
@sibling.merge(self)
|
378
|
+
|
379
|
+
force_reset(true)
|
380
|
+
end
|
381
|
+
|
298
382
|
private
|
299
383
|
|
300
384
|
def connect
|
301
385
|
transition(:open)
|
302
386
|
end
|
303
387
|
|
388
|
+
def disconnect
|
389
|
+
emit(:close)
|
390
|
+
@current_session = nil
|
391
|
+
@current_selector = nil
|
392
|
+
end
|
393
|
+
|
304
394
|
def consume
|
305
395
|
return unless @io
|
306
396
|
|
@@ -475,8 +565,27 @@ module HTTPX
|
|
475
565
|
request.emit(:promise, parser, stream)
|
476
566
|
end
|
477
567
|
parser.on(:exhausted) do
|
478
|
-
@
|
479
|
-
|
568
|
+
@exhausted = true
|
569
|
+
current_session = @current_session
|
570
|
+
current_selector = @current_selector
|
571
|
+
begin
|
572
|
+
parser.close
|
573
|
+
@pending.concat(parser.pending)
|
574
|
+
ensure
|
575
|
+
@current_session = current_session
|
576
|
+
@current_selector = current_selector
|
577
|
+
end
|
578
|
+
|
579
|
+
case @state
|
580
|
+
when :closed
|
581
|
+
idling
|
582
|
+
@exhausted = false
|
583
|
+
when :closing
|
584
|
+
once(:closed) do
|
585
|
+
idling
|
586
|
+
@exhausted = false
|
587
|
+
end
|
588
|
+
end
|
480
589
|
end
|
481
590
|
parser.on(:origin) do |origin|
|
482
591
|
@origins |= [origin]
|
@@ -492,8 +601,14 @@ module HTTPX
|
|
492
601
|
end
|
493
602
|
parser.on(:reset) do
|
494
603
|
@pending.concat(parser.pending) unless parser.empty?
|
604
|
+
current_session = @current_session
|
605
|
+
current_selector = @current_selector
|
495
606
|
reset
|
496
|
-
|
607
|
+
unless @pending.empty?
|
608
|
+
idling
|
609
|
+
@current_session = current_session
|
610
|
+
@current_selector = current_selector
|
611
|
+
end
|
497
612
|
end
|
498
613
|
parser.on(:current_timeout) do
|
499
614
|
@current_timeout = @timeout = parser.timeout
|
@@ -504,7 +619,15 @@ module HTTPX
|
|
504
619
|
parser.on(:error) do |request, ex|
|
505
620
|
case ex
|
506
621
|
when MisdirectedRequestError
|
507
|
-
|
622
|
+
current_session = @current_session
|
623
|
+
current_selector = @current_selector
|
624
|
+
parser.close
|
625
|
+
|
626
|
+
other_connection = current_session.find_connection(@origin, current_selector,
|
627
|
+
@options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
|
628
|
+
other_connection.merge(self)
|
629
|
+
request.transition(:idle)
|
630
|
+
other_connection.send(request)
|
508
631
|
else
|
509
632
|
response = ErrorResponse.new(request, ex)
|
510
633
|
request.response = response
|
@@ -529,15 +652,15 @@ module HTTPX
|
|
529
652
|
# connect errors, exit gracefully
|
530
653
|
error = ConnectionError.new(e.message)
|
531
654
|
error.set_backtrace(e.backtrace)
|
532
|
-
|
655
|
+
handle_connect_error(error) if connecting?
|
533
656
|
@state = :closed
|
534
|
-
|
657
|
+
disconnect
|
535
658
|
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
536
659
|
# connect errors, exit gracefully
|
537
660
|
handle_error(e)
|
538
|
-
|
661
|
+
handle_connect_error(e) if connecting?
|
539
662
|
@state = :closed
|
540
|
-
|
663
|
+
disconnect
|
541
664
|
end
|
542
665
|
|
543
666
|
def handle_transition(nextstate)
|
@@ -550,7 +673,7 @@ module HTTPX
|
|
550
673
|
return if @state == :closed
|
551
674
|
|
552
675
|
@io.connect
|
553
|
-
|
676
|
+
close_sibling if @io.state == :connected
|
554
677
|
|
555
678
|
return unless @io.connected?
|
556
679
|
|
@@ -582,7 +705,8 @@ module HTTPX
|
|
582
705
|
return unless @write_buffer.empty?
|
583
706
|
|
584
707
|
purge_after_closed
|
585
|
-
|
708
|
+
disconnect if @pending.empty?
|
709
|
+
|
586
710
|
when :already_open
|
587
711
|
nextstate = :open
|
588
712
|
# the first check for given io readiness must still use a timeout.
|
@@ -593,11 +717,29 @@ module HTTPX
|
|
593
717
|
return unless @state == :inactive
|
594
718
|
|
595
719
|
nextstate = :open
|
596
|
-
|
720
|
+
|
721
|
+
# activate
|
722
|
+
@current_session.select_connection(self, @current_selector)
|
597
723
|
end
|
598
724
|
@state = nextstate
|
599
725
|
end
|
600
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
|
+
|
601
743
|
def purge_after_closed
|
602
744
|
@io.close if @io
|
603
745
|
@read_buffer.clear
|
@@ -617,12 +759,40 @@ module HTTPX
|
|
617
759
|
end
|
618
760
|
end
|
619
761
|
|
762
|
+
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
763
|
+
def build_altsvc_connection(alt_origin, origin, alt_params)
|
764
|
+
# do not allow security downgrades on altsvc negotiation
|
765
|
+
return if @origin.scheme == "https" && alt_origin.scheme != "https"
|
766
|
+
|
767
|
+
altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
|
768
|
+
|
769
|
+
# altsvc already exists, somehow it wasn't advertised, probably noop
|
770
|
+
return unless altsvc
|
771
|
+
|
772
|
+
alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
|
773
|
+
|
774
|
+
connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
|
775
|
+
|
776
|
+
# advertised altsvc is the same origin being used, ignore
|
777
|
+
return if connection == self
|
778
|
+
|
779
|
+
connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
|
780
|
+
|
781
|
+
log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
|
782
|
+
|
783
|
+
connection.merge(self)
|
784
|
+
terminate
|
785
|
+
rescue UnsupportedSchemeError
|
786
|
+
altsvc["noop"] = true
|
787
|
+
nil
|
788
|
+
end
|
789
|
+
|
620
790
|
def build_socket(addrs = nil)
|
621
791
|
case @type
|
622
792
|
when "tcp"
|
623
|
-
TCP.new(
|
793
|
+
TCP.new(peer, addrs, @options)
|
624
794
|
when "ssl"
|
625
|
-
SSL.new(
|
795
|
+
SSL.new(peer, addrs, @options) do |sock|
|
626
796
|
sock.ssl_session = @ssl_session
|
627
797
|
sock.session_new_cb do |sess|
|
628
798
|
@ssl_session = sess
|
@@ -635,7 +805,7 @@ module HTTPX
|
|
635
805
|
|
636
806
|
path = String(path) if path
|
637
807
|
|
638
|
-
UNIX.new(
|
808
|
+
UNIX.new(peer, path, @options)
|
639
809
|
else
|
640
810
|
raise Error, "unsupported transport (#{@type})"
|
641
811
|
end
|
@@ -734,7 +904,7 @@ module HTTPX
|
|
734
904
|
|
735
905
|
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
736
906
|
request.once(start_event) do
|
737
|
-
interval = @
|
907
|
+
interval = @current_selector.after(timeout, callback)
|
738
908
|
|
739
909
|
Array(finish_events).each do |event|
|
740
910
|
# clean up request timeouts if the connection errors out
|
data/lib/httpx/errors.rb
CHANGED
@@ -29,6 +29,18 @@ module HTTPX
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
# Raise when it can't acquire a connection for a given origin.
|
33
|
+
class PoolTimeoutError < TimeoutError
|
34
|
+
attr_reader :origin
|
35
|
+
|
36
|
+
# initializes the +origin+ it refers to, and the
|
37
|
+
# +timeout+ causing the error.
|
38
|
+
def initialize(origin, timeout)
|
39
|
+
@origin = origin
|
40
|
+
super(timeout, "Timed out after #{timeout} seconds while waiting for a connection to #{origin}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
32
44
|
# Error raised when there was a timeout establishing the connection to a server.
|
33
45
|
# This may be raised due to timeouts during TCP and TLS (when applicable) connection
|
34
46
|
# establishment.
|
data/lib/httpx/loggable.rb
CHANGED
@@ -13,11 +13,14 @@ module HTTPX
|
|
13
13
|
white: 37,
|
14
14
|
}.freeze
|
15
15
|
|
16
|
+
USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
|
17
|
+
|
16
18
|
def log(level: @options.debug_level, color: nil, &msg)
|
17
|
-
return unless @options.debug
|
18
19
|
return unless @options.debug_level >= level
|
19
20
|
|
20
|
-
debug_stream = @options.debug
|
21
|
+
debug_stream = @options.debug || ($stderr if USE_DEBUG_LOG)
|
22
|
+
|
23
|
+
return unless debug_stream
|
21
24
|
|
22
25
|
message = (+"" << msg.call << "\n")
|
23
26
|
message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
|
@@ -25,9 +28,6 @@ module HTTPX
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def log_exception(ex, level: @options.debug_level, color: nil)
|
28
|
-
return unless @options.debug
|
29
|
-
return unless @options.debug_level >= level
|
30
|
-
|
31
31
|
log(level: level, color: color) { ex.full_message }
|
32
32
|
end
|
33
33
|
end
|
data/lib/httpx/options.rb
CHANGED
@@ -25,14 +25,14 @@ module HTTPX
|
|
25
25
|
end
|
26
26
|
rescue NotImplementedError
|
27
27
|
[Socket::AF_INET]
|
28
|
-
end
|
28
|
+
end.freeze
|
29
29
|
|
30
30
|
DEFAULT_OPTIONS = {
|
31
31
|
:max_requests => Float::INFINITY,
|
32
|
-
:debug =>
|
32
|
+
:debug => nil,
|
33
33
|
:debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
|
34
|
-
:ssl =>
|
35
|
-
:http2_settings => { settings_enable_push: 0 },
|
34
|
+
:ssl => EMPTY_HASH,
|
35
|
+
:http2_settings => { settings_enable_push: 0 }.freeze,
|
36
36
|
:fallback_protocol => "http/1.1",
|
37
37
|
:supported_compression_formats => %w[gzip deflate],
|
38
38
|
:decompress_response_body => true,
|
@@ -56,13 +56,15 @@ module HTTPX
|
|
56
56
|
:response_class => Class.new(Response),
|
57
57
|
:request_body_class => Class.new(Request::Body),
|
58
58
|
:response_body_class => Class.new(Response::Body),
|
59
|
+
:pool_class => Class.new(Pool),
|
59
60
|
:connection_class => Class.new(Connection),
|
60
61
|
:options_class => Class.new(self),
|
61
62
|
:transport => nil,
|
62
63
|
:addresses => nil,
|
63
64
|
:persistent => false,
|
64
65
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
65
|
-
:resolver_options => { cache: true },
|
66
|
+
:resolver_options => { cache: true }.freeze,
|
67
|
+
:pool_options => EMPTY_HASH,
|
66
68
|
:ip_families => ip_address_families,
|
67
69
|
}.freeze
|
68
70
|
|
@@ -110,6 +112,7 @@ module HTTPX
|
|
110
112
|
# :request_body_class :: class used to instantiate a request body
|
111
113
|
# :response_body_class :: class used to instantiate a response body
|
112
114
|
# :connection_class :: class used to instantiate connections
|
115
|
+
# :pool_class :: class used to instantiate the session connection pool
|
113
116
|
# :options_class :: class used to instantiate options
|
114
117
|
# :transport :: type of transport to use (set to "unix" for UNIX sockets)
|
115
118
|
# :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
|
@@ -118,7 +121,8 @@ module HTTPX
|
|
118
121
|
# :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
|
119
122
|
# :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
|
120
123
|
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
|
121
|
-
# :resolver_options :: hash of options passed to the resolver
|
124
|
+
# :resolver_options :: hash of options passed to the resolver. Accepted keys depend on the resolver type.
|
125
|
+
# :pool_options :: hash of options passed to the connection pool (See Pool#initialize).
|
122
126
|
# :ip_families :: which socket families are supported (system-dependent)
|
123
127
|
# :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
|
124
128
|
# :base_path :: path to prefix given relative paths with (ex: "/v2")
|
@@ -215,6 +219,7 @@ module HTTPX
|
|
215
219
|
ssl http2_settings
|
216
220
|
request_class response_class headers_class request_body_class
|
217
221
|
response_body_class connection_class options_class
|
222
|
+
pool_class pool_options
|
218
223
|
io fallback_protocol debug debug_level resolver_class resolver_options
|
219
224
|
compress_request_body decompress_response_body
|
220
225
|
persistent
|
@@ -246,14 +251,6 @@ module HTTPX
|
|
246
251
|
end
|
247
252
|
end
|
248
253
|
|
249
|
-
OTHER_LOOKUP = ->(obj, k, ivar_map) {
|
250
|
-
case obj
|
251
|
-
when Hash
|
252
|
-
obj[ivar_map[k]]
|
253
|
-
else
|
254
|
-
obj.instance_variable_get(k)
|
255
|
-
end
|
256
|
-
}
|
257
254
|
def merge(other)
|
258
255
|
ivar_map = nil
|
259
256
|
other_ivars = case other
|
@@ -266,12 +263,12 @@ module HTTPX
|
|
266
263
|
|
267
264
|
return self if other_ivars.empty?
|
268
265
|
|
269
|
-
return self if other_ivars.all? { |ivar| instance_variable_get(ivar) ==
|
266
|
+
return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == access_option(other, ivar, ivar_map) }
|
270
267
|
|
271
268
|
opts = dup
|
272
269
|
|
273
270
|
other_ivars.each do |ivar|
|
274
|
-
v =
|
271
|
+
v = access_option(other, ivar, ivar_map)
|
275
272
|
|
276
273
|
unless v
|
277
274
|
opts.instance_variable_set(ivar, v)
|
@@ -322,6 +319,10 @@ module HTTPX
|
|
322
319
|
@response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
|
323
320
|
@response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
|
324
321
|
end
|
322
|
+
if defined?(pl::PoolMethods)
|
323
|
+
@pool_class = @pool_class.dup
|
324
|
+
@pool_class.__send__(:include, pl::PoolMethods)
|
325
|
+
end
|
325
326
|
if defined?(pl::ConnectionMethods)
|
326
327
|
@connection_class = @connection_class.dup
|
327
328
|
@connection_class.__send__(:include, pl::ConnectionMethods)
|
@@ -346,5 +347,14 @@ module HTTPX
|
|
346
347
|
instance_variable_set(:"@#{k}", value)
|
347
348
|
end
|
348
349
|
end
|
350
|
+
|
351
|
+
def access_option(obj, k, ivar_map)
|
352
|
+
case obj
|
353
|
+
when Hash
|
354
|
+
obj[ivar_map[k]]
|
355
|
+
else
|
356
|
+
obj.instance_variable_get(k)
|
357
|
+
end
|
358
|
+
end
|
349
359
|
end
|
350
360
|
end
|
@@ -89,7 +89,7 @@ module HTTPX
|
|
89
89
|
sts = "#{algo_line}" \
|
90
90
|
"\n#{datetime}" \
|
91
91
|
"\n#{credential_scope}" \
|
92
|
-
"\n#{hexdigest(creq)}"
|
92
|
+
"\n#{OpenSSL::Digest.new(@algorithm).hexdigest(creq)}"
|
93
93
|
|
94
94
|
# signature
|
95
95
|
k_date = hmac("#{upper_provider_prefix}#{@credentials.password}", date)
|
@@ -110,22 +110,38 @@ module HTTPX
|
|
110
110
|
private
|
111
111
|
|
112
112
|
def hexdigest(value)
|
113
|
-
|
114
|
-
# files, pathnames
|
115
|
-
OpenSSL::Digest.new(@algorithm).file(value.to_path).hexdigest
|
116
|
-
elsif value.respond_to?(:each)
|
117
|
-
digest = OpenSSL::Digest.new(@algorithm)
|
118
|
-
|
119
|
-
mb_buffer = value.each.with_object("".b) do |chunk, buffer|
|
120
|
-
buffer << chunk
|
121
|
-
break if buffer.bytesize >= 1024 * 1024
|
122
|
-
end
|
113
|
+
digest = OpenSSL::Digest.new(@algorithm)
|
123
114
|
|
124
|
-
|
125
|
-
value.
|
126
|
-
|
115
|
+
if value.respond_to?(:read)
|
116
|
+
if value.respond_to?(:to_path)
|
117
|
+
# files, pathnames
|
118
|
+
digest.file(value.to_path).hexdigest
|
119
|
+
else
|
120
|
+
# gzipped request bodies
|
121
|
+
raise Error, "request body must be rewindable" unless value.respond_to?(:rewind)
|
122
|
+
|
123
|
+
buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
124
|
+
begin
|
125
|
+
IO.copy_stream(value, buffer)
|
126
|
+
buffer.flush
|
127
|
+
|
128
|
+
digest.file(buffer.to_path).hexdigest
|
129
|
+
ensure
|
130
|
+
value.rewind
|
131
|
+
buffer.close
|
132
|
+
buffer.unlink
|
133
|
+
end
|
134
|
+
end
|
127
135
|
else
|
128
|
-
|
136
|
+
# error on endless generators
|
137
|
+
raise Error, "hexdigest for endless enumerators is not supported" if value.unbounded_body?
|
138
|
+
|
139
|
+
mb_buffer = value.each.with_object("".b) do |chunk, b|
|
140
|
+
b << chunk
|
141
|
+
break if b.bytesize >= 1024 * 1024
|
142
|
+
end
|
143
|
+
|
144
|
+
digest.hexdigest(mb_buffer)
|
129
145
|
end
|
130
146
|
end
|
131
147
|
|
@@ -142,7 +158,6 @@ module HTTPX
|
|
142
158
|
def load_dependencies(*)
|
143
159
|
require "set"
|
144
160
|
require "digest/sha2"
|
145
|
-
require "openssl"
|
146
161
|
end
|
147
162
|
|
148
163
|
def configure(klass)
|
@@ -31,12 +31,16 @@ module HTTPX
|
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
def
|
35
|
-
|
34
|
+
def do_init_connection(connection, selector)
|
35
|
+
super
|
36
36
|
connection.on(:open) do
|
37
|
+
next unless connection.current_session == self
|
38
|
+
|
37
39
|
emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
|
38
40
|
end
|
39
41
|
connection.on(:close) do
|
42
|
+
next unless connection.current_session == self
|
43
|
+
|
40
44
|
emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
|
41
45
|
end
|
42
46
|
|
@@ -84,6 +88,12 @@ module HTTPX
|
|
84
88
|
rescue CallbackError => e
|
85
89
|
raise e.cause
|
86
90
|
end
|
91
|
+
|
92
|
+
def close(*)
|
93
|
+
super
|
94
|
+
rescue CallbackError => e
|
95
|
+
raise e.cause
|
96
|
+
end
|
87
97
|
end
|
88
98
|
end
|
89
99
|
register_plugin :callbacks, Callbacks
|
@@ -32,11 +32,6 @@ module HTTPX
|
|
32
32
|
@circuit_store = CircuitStore.new(@options)
|
33
33
|
end
|
34
34
|
|
35
|
-
def initialize_dup(orig)
|
36
|
-
super
|
37
|
-
@circuit_store = orig.instance_variable_get(:@circuit_store).dup
|
38
|
-
end
|
39
|
-
|
40
35
|
%i[circuit_open].each do |meth|
|
41
36
|
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
42
37
|
def on_#{meth}(&blk) # def on_circuit_open(&blk)
|