httpx 0.18.0 → 0.19.3
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 +9 -4
- data/doc/release_notes/0_18_1.md +12 -0
- data/doc/release_notes/0_18_2.md +10 -0
- data/doc/release_notes/0_18_3.md +7 -0
- data/doc/release_notes/0_18_4.md +14 -0
- data/doc/release_notes/0_18_5.md +10 -0
- data/doc/release_notes/0_18_6.md +5 -0
- data/doc/release_notes/0_18_7.md +5 -0
- data/doc/release_notes/0_19_0.md +39 -0
- data/doc/release_notes/0_19_1.md +5 -0
- data/doc/release_notes/0_19_2.md +7 -0
- data/doc/release_notes/0_19_3.md +6 -0
- data/lib/httpx/adapters/faraday.rb +58 -12
- data/lib/httpx/adapters/webmock.rb +71 -59
- data/lib/httpx/altsvc.rb +25 -9
- data/lib/httpx/connection/http1.rb +10 -7
- data/lib/httpx/connection/http2.rb +23 -8
- data/lib/httpx/connection.rb +26 -12
- data/lib/httpx/extensions.rb +16 -0
- data/lib/httpx/headers.rb +0 -2
- data/lib/httpx/io/ssl.rb +4 -0
- data/lib/httpx/io/tcp.rb +27 -6
- data/lib/httpx/io/udp.rb +0 -1
- data/lib/httpx/options.rb +44 -11
- data/lib/httpx/plugins/cookies.rb +5 -7
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +18 -4
- data/lib/httpx/plugins/proxy/http.rb +10 -23
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +35 -15
- data/lib/httpx/plugins/retries.rb +15 -12
- data/lib/httpx/pool.rb +40 -20
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +32 -42
- data/lib/httpx/resolver/multi.rb +79 -0
- data/lib/httpx/resolver/native.rb +28 -36
- data/lib/httpx/resolver/resolver.rb +95 -0
- data/lib/httpx/resolver/system.rb +175 -19
- data/lib/httpx/resolver.rb +37 -11
- data/lib/httpx/response.rb +4 -2
- data/lib/httpx/selector.rb +7 -0
- data/lib/httpx/session.rb +2 -16
- data/lib/httpx/session_extensions.rb +26 -0
- data/lib/httpx/timers.rb +1 -1
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +3 -0
- data/sig/connection/http1.rbs +5 -2
- data/sig/connection/http2.rbs +5 -2
- data/sig/connection.rbs +1 -0
- data/sig/errors.rbs +8 -0
- data/sig/headers.rbs +0 -2
- data/sig/httpx.rbs +4 -0
- data/sig/options.rbs +10 -7
- data/sig/parser/http1.rbs +14 -5
- data/sig/pool.rbs +17 -9
- data/sig/registry.rbs +3 -0
- data/sig/request.rbs +11 -0
- data/sig/resolver/https.rbs +15 -27
- data/sig/resolver/multi.rbs +7 -0
- data/sig/resolver/native.rbs +3 -12
- data/sig/resolver/resolver.rbs +36 -0
- data/sig/resolver/system.rbs +3 -9
- data/sig/resolver.rbs +12 -10
- data/sig/response.rbs +15 -5
- data/sig/selector.rbs +3 -3
- data/sig/timers.rbs +5 -2
- data/sig/transcoder/chunker.rbs +16 -5
- data/sig/transcoder/json.rbs +5 -0
- data/sig/transcoder.rbs +3 -1
- metadata +31 -5
- data/lib/httpx/resolver/resolver_mixin.rb +0 -75
- data/sig/resolver/resolver_mixin.rbs +0 -26
data/lib/httpx/connection.rb
CHANGED
@@ -44,7 +44,7 @@ module HTTPX
|
|
44
44
|
|
45
45
|
def_delegator :@write_buffer, :empty?
|
46
46
|
|
47
|
-
attr_reader :origin, :state, :pending, :options
|
47
|
+
attr_reader :io, :origin, :origins, :state, :pending, :options
|
48
48
|
|
49
49
|
attr_writer :timers
|
50
50
|
|
@@ -78,7 +78,11 @@ module HTTPX
|
|
78
78
|
# this is a semi-private method, to be used by the resolver
|
79
79
|
# to initiate the io object.
|
80
80
|
def addresses=(addrs)
|
81
|
-
@io
|
81
|
+
if @io
|
82
|
+
@io.add_addresses(addrs)
|
83
|
+
else
|
84
|
+
@io = IO.registry(@type).new(@origin, addrs, @options)
|
85
|
+
end
|
82
86
|
end
|
83
87
|
|
84
88
|
def addresses
|
@@ -117,7 +121,8 @@ module HTTPX
|
|
117
121
|
def coalescable?(connection)
|
118
122
|
if @io.protocol == "h2" &&
|
119
123
|
@origin.scheme == "https" &&
|
120
|
-
connection.origin.scheme == "https"
|
124
|
+
connection.origin.scheme == "https" &&
|
125
|
+
@io.can_verify_peer?
|
121
126
|
@io.verify_hostname(connection.origin.host)
|
122
127
|
else
|
123
128
|
@origin == connection.origin
|
@@ -241,7 +246,7 @@ module HTTPX
|
|
241
246
|
if elapsed_time.negative?
|
242
247
|
ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
|
243
248
|
ex.set_backtrace(caller)
|
244
|
-
on_error(
|
249
|
+
on_error(ex)
|
245
250
|
return
|
246
251
|
end
|
247
252
|
|
@@ -463,6 +468,7 @@ module HTTPX
|
|
463
468
|
transition(:closing)
|
464
469
|
transition(:closed)
|
465
470
|
emit(:reset)
|
471
|
+
|
466
472
|
@parser.reset if @parser
|
467
473
|
transition(:idle)
|
468
474
|
transition(:open)
|
@@ -487,6 +493,18 @@ module HTTPX
|
|
487
493
|
end
|
488
494
|
|
489
495
|
def transition(nextstate)
|
496
|
+
handle_transition(nextstate)
|
497
|
+
rescue Errno::ECONNREFUSED,
|
498
|
+
Errno::EADDRNOTAVAIL,
|
499
|
+
Errno::EHOSTUNREACH,
|
500
|
+
TLSError => e
|
501
|
+
# connect errors, exit gracefully
|
502
|
+
handle_error(e)
|
503
|
+
@state = :closed
|
504
|
+
emit(:close)
|
505
|
+
end
|
506
|
+
|
507
|
+
def handle_transition(nextstate)
|
490
508
|
case nextstate
|
491
509
|
when :idle
|
492
510
|
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
@@ -523,14 +541,6 @@ module HTTPX
|
|
523
541
|
emit(:activate)
|
524
542
|
end
|
525
543
|
@state = nextstate
|
526
|
-
rescue Errno::ECONNREFUSED,
|
527
|
-
Errno::EADDRNOTAVAIL,
|
528
|
-
Errno::EHOSTUNREACH,
|
529
|
-
TLSError => e
|
530
|
-
# connect errors, exit gracefully
|
531
|
-
handle_error(e)
|
532
|
-
@state = :closed
|
533
|
-
emit(:close)
|
534
544
|
end
|
535
545
|
|
536
546
|
def purge_after_closed
|
@@ -548,6 +558,10 @@ module HTTPX
|
|
548
558
|
ex.set_backtrace(error.backtrace)
|
549
559
|
error = ex
|
550
560
|
else
|
561
|
+
# inactive connections do not contribute to the select loop, therefore
|
562
|
+
# they should fail due to such errors.
|
563
|
+
return if @state == :inactive
|
564
|
+
|
551
565
|
if @timeout
|
552
566
|
@timeout -= error.timeout
|
553
567
|
return unless @timeout <= 0
|
data/lib/httpx/extensions.rb
CHANGED
@@ -54,6 +54,22 @@ module HTTPX
|
|
54
54
|
Numeric.__send__(:include, NegMethods)
|
55
55
|
end
|
56
56
|
|
57
|
+
module StringExtensions
|
58
|
+
refine String do
|
59
|
+
def delete_suffix!(suffix)
|
60
|
+
suffix = Backports.coerce_to_str(suffix)
|
61
|
+
chomp! if frozen?
|
62
|
+
len = suffix.length
|
63
|
+
if len > 0 && index(suffix, -len)
|
64
|
+
self[-len..-1] = ''
|
65
|
+
self
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end unless String.method_defined?(:delete_suffix!)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
57
73
|
module HashExtensions
|
58
74
|
refine Hash do
|
59
75
|
def compact
|
data/lib/httpx/headers.rb
CHANGED
data/lib/httpx/io/ssl.rb
CHANGED
@@ -27,6 +27,10 @@ module HTTPX
|
|
27
27
|
super
|
28
28
|
end
|
29
29
|
|
30
|
+
def can_verify_peer?
|
31
|
+
@ctx.verify_mode == OpenSSL::SSL::VERIFY_PEER
|
32
|
+
end
|
33
|
+
|
30
34
|
def verify_hostname(host)
|
31
35
|
return false if @ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
32
36
|
return false if !@io.respond_to?(:peer_cert) || @io.peer_cert.nil?
|
data/lib/httpx/io/tcp.rb
CHANGED
@@ -15,6 +15,7 @@ module HTTPX
|
|
15
15
|
|
16
16
|
def initialize(origin, addresses, options)
|
17
17
|
@state = :idle
|
18
|
+
@addresses = []
|
18
19
|
@hostname = origin.host
|
19
20
|
@options = Options.new(options)
|
20
21
|
@fallback_protocol = @options.fallback_protocol
|
@@ -30,15 +31,29 @@ module HTTPX
|
|
30
31
|
raise Error, "Given IO objects do not match the request authority" unless @io
|
31
32
|
|
32
33
|
_, _, _, @ip = @io.addr
|
33
|
-
@addresses
|
34
|
-
@ip_index = @addresses.size - 1
|
34
|
+
@addresses << @ip
|
35
35
|
@keep_open = true
|
36
36
|
@state = :connected
|
37
37
|
else
|
38
|
-
|
38
|
+
add_addresses(addresses)
|
39
39
|
end
|
40
40
|
@ip_index = @addresses.size - 1
|
41
|
-
@io ||= build_socket
|
41
|
+
# @io ||= build_socket
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_addresses(addrs)
|
45
|
+
return if addrs.empty?
|
46
|
+
|
47
|
+
addrs = addrs.map { |addr| addr.is_a?(IPAddr) ? addr : IPAddr.new(addr) }
|
48
|
+
|
49
|
+
ip_index = @ip_index || (@addresses.size - 1)
|
50
|
+
if addrs.first.ipv6?
|
51
|
+
# should be the next in line
|
52
|
+
@addresses = [*@addresses[0, ip_index], *addrs, *@addresses[ip_index..-1]]
|
53
|
+
else
|
54
|
+
@addresses.unshift(*addrs)
|
55
|
+
@ip_index += addrs.size if @ip_index
|
56
|
+
end
|
42
57
|
end
|
43
58
|
|
44
59
|
def to_io
|
@@ -52,20 +67,26 @@ module HTTPX
|
|
52
67
|
def connect
|
53
68
|
return unless closed?
|
54
69
|
|
55
|
-
if @io.closed?
|
70
|
+
if !@io || @io.closed?
|
56
71
|
transition(:idle)
|
57
72
|
@io = build_socket
|
58
73
|
end
|
59
74
|
try_connect
|
60
|
-
rescue Errno::
|
75
|
+
rescue Errno::ECONNREFUSED,
|
76
|
+
Errno::EADDRNOTAVAIL,
|
77
|
+
Errno::EHOSTUNREACH => e
|
61
78
|
raise e if @ip_index <= 0
|
62
79
|
|
80
|
+
log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
|
63
81
|
@ip_index -= 1
|
82
|
+
@io = build_socket
|
64
83
|
retry
|
65
84
|
rescue Errno::ETIMEDOUT => e
|
66
85
|
raise ConnectTimeoutError.new(@options.timeout[:connect_timeout], e.message) if @ip_index <= 0
|
67
86
|
|
87
|
+
log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
|
68
88
|
@ip_index -= 1
|
89
|
+
@io = build_socket
|
69
90
|
retry
|
70
91
|
end
|
71
92
|
|
data/lib/httpx/io/udp.rb
CHANGED
data/lib/httpx/options.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "socket"
|
4
|
+
|
3
5
|
module HTTPX
|
4
6
|
class Options
|
5
7
|
WINDOW_SIZE = 1 << 14 # 16K
|
@@ -9,6 +11,18 @@ module HTTPX
|
|
9
11
|
KEEP_ALIVE_TIMEOUT = 20
|
10
12
|
SETTINGS_TIMEOUT = 10
|
11
13
|
|
14
|
+
# https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
|
15
|
+
ip_address_families = begin
|
16
|
+
list = Socket.ip_address_list
|
17
|
+
if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
|
18
|
+
[Socket::AF_INET6, Socket::AF_INET]
|
19
|
+
else
|
20
|
+
[Socket::AF_INET]
|
21
|
+
end
|
22
|
+
rescue NotImplementedError
|
23
|
+
[Socket::AF_INET]
|
24
|
+
end
|
25
|
+
|
12
26
|
DEFAULT_OPTIONS = {
|
13
27
|
:debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
|
14
28
|
:debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
|
@@ -37,6 +51,7 @@ module HTTPX
|
|
37
51
|
:persistent => false,
|
38
52
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
39
53
|
:resolver_options => { cache: true },
|
54
|
+
:ip_families => ip_address_families,
|
40
55
|
}.freeze
|
41
56
|
|
42
57
|
begin
|
@@ -110,20 +125,18 @@ module HTTPX
|
|
110
125
|
end
|
111
126
|
|
112
127
|
def initialize(options = {})
|
113
|
-
|
114
|
-
defaults.each do |k, v|
|
115
|
-
next if v.nil?
|
116
|
-
|
117
|
-
begin
|
118
|
-
value = __send__(:"option_#{k}", v)
|
119
|
-
instance_variable_set(:"@#{k}", value)
|
120
|
-
rescue NoMethodError
|
121
|
-
raise Error, "unknown option: #{k}"
|
122
|
-
end
|
123
|
-
end
|
128
|
+
__initialize__(options)
|
124
129
|
freeze
|
125
130
|
end
|
126
131
|
|
132
|
+
def freeze
|
133
|
+
super
|
134
|
+
@origin.freeze
|
135
|
+
@timeout.freeze
|
136
|
+
@headers.freeze
|
137
|
+
@addresses.freeze
|
138
|
+
end
|
139
|
+
|
127
140
|
def option_origin(value)
|
128
141
|
URI(value)
|
129
142
|
end
|
@@ -174,6 +187,10 @@ module HTTPX
|
|
174
187
|
Array(value)
|
175
188
|
end
|
176
189
|
|
190
|
+
def option_ip_families(value)
|
191
|
+
Array(value)
|
192
|
+
end
|
193
|
+
|
177
194
|
%i[
|
178
195
|
params form json body ssl http2_settings
|
179
196
|
request_class response_class headers_class request_body_class
|
@@ -249,5 +266,21 @@ module HTTPX
|
|
249
266
|
end
|
250
267
|
end
|
251
268
|
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def __initialize__(options = {})
|
273
|
+
defaults = DEFAULT_OPTIONS.merge(options)
|
274
|
+
defaults.each do |k, v|
|
275
|
+
next if v.nil?
|
276
|
+
|
277
|
+
begin
|
278
|
+
value = __send__(:"option_#{k}", v)
|
279
|
+
instance_variable_set(:"@#{k}", value)
|
280
|
+
rescue NoMethodError
|
281
|
+
raise Error, "unknown option: #{k}"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
252
285
|
end
|
253
286
|
end
|
@@ -18,12 +18,6 @@ module HTTPX
|
|
18
18
|
require "httpx/plugins/cookies/set_cookie_parser"
|
19
19
|
end
|
20
20
|
|
21
|
-
module OptionsMethods
|
22
|
-
def option_cookies(value)
|
23
|
-
value.is_a?(Jar) ? value : Jar.new(value)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
21
|
module InstanceMethods
|
28
22
|
extend Forwardable
|
29
23
|
|
@@ -77,7 +71,7 @@ module HTTPX
|
|
77
71
|
end
|
78
72
|
|
79
73
|
module OptionsMethods
|
80
|
-
def
|
74
|
+
def __initialize__(*)
|
81
75
|
super
|
82
76
|
|
83
77
|
return unless @headers.key?("cookie")
|
@@ -89,6 +83,10 @@ module HTTPX
|
|
89
83
|
end
|
90
84
|
end
|
91
85
|
end
|
86
|
+
|
87
|
+
def option_cookies(value)
|
88
|
+
value.is_a?(Jar) ? value : Jar.new(value)
|
89
|
+
end
|
92
90
|
end
|
93
91
|
end
|
94
92
|
register_plugin :cookies, Cookies
|
@@ -8,11 +8,25 @@ module HTTPX
|
|
8
8
|
DEFAULT_MIMETYPE = "application/octet-stream"
|
9
9
|
|
10
10
|
# inspired by https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/determine_mime_type.rb
|
11
|
-
if defined?(
|
11
|
+
if defined?(FileMagic)
|
12
|
+
MAGIC_NUMBER = 256 * 1024
|
12
13
|
|
13
|
-
def call(
|
14
|
-
|
15
|
-
|
14
|
+
def call(file, _)
|
15
|
+
return nil if file.eof? # FileMagic returns "application/x-empty" for empty files
|
16
|
+
|
17
|
+
mime = FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
|
18
|
+
filemagic.buffer(file.read(MAGIC_NUMBER))
|
19
|
+
end
|
20
|
+
|
21
|
+
file.rewind
|
22
|
+
|
23
|
+
mime
|
24
|
+
end
|
25
|
+
elsif defined?(Marcel)
|
26
|
+
def call(file, filename)
|
27
|
+
return nil if file.eof? # marcel returns "application/octet-stream" for empty files
|
28
|
+
|
29
|
+
Marcel::MimeType.for(file, name: filename)
|
16
30
|
end
|
17
31
|
|
18
32
|
elsif defined?(MimeMagic)
|
@@ -13,7 +13,7 @@ module HTTPX
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def
|
16
|
+
def handle_transition(nextstate)
|
17
17
|
return super unless @options.proxy && @options.proxy.uri.scheme == "http"
|
18
18
|
|
19
19
|
case nextstate
|
@@ -23,7 +23,8 @@ module HTTPX
|
|
23
23
|
@io.connect
|
24
24
|
return unless @io.connected?
|
25
25
|
|
26
|
-
@parser =
|
26
|
+
@parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
27
|
+
@parser.extend(ProxyParser)
|
27
28
|
@parser.once(:response, &method(:__http_on_connect))
|
28
29
|
@parser.on(:close) { transition(:closing) }
|
29
30
|
__http_proxy_connect
|
@@ -36,7 +37,7 @@ module HTTPX
|
|
36
37
|
@parser.close
|
37
38
|
@parser = nil
|
38
39
|
when :idle
|
39
|
-
@parser
|
40
|
+
@parser.callbacks.clear
|
40
41
|
set_parser_callbacks(@parser)
|
41
42
|
end
|
42
43
|
end
|
@@ -54,7 +55,7 @@ module HTTPX
|
|
54
55
|
@inflight += 1
|
55
56
|
parser.send(connect_request)
|
56
57
|
else
|
57
|
-
|
58
|
+
handle_transition(:connected)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
@@ -76,9 +77,11 @@ module HTTPX
|
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
79
|
-
|
80
|
-
def
|
81
|
-
request.
|
80
|
+
module ProxyParser
|
81
|
+
def join_headline(request)
|
82
|
+
return super if request.verb == :connect
|
83
|
+
|
84
|
+
"#{request.verb.to_s.upcase} #{request.uri} HTTP/#{@version.join(".")}"
|
82
85
|
end
|
83
86
|
|
84
87
|
def set_protocol_headers(request)
|
@@ -91,22 +94,6 @@ module HTTPX
|
|
91
94
|
end
|
92
95
|
end
|
93
96
|
|
94
|
-
class ConnectProxyParser < ProxyParser
|
95
|
-
attr_reader :pending
|
96
|
-
|
97
|
-
def headline_uri(request)
|
98
|
-
return super unless request.verb == :connect
|
99
|
-
|
100
|
-
tunnel = request.path
|
101
|
-
log { "establishing HTTP proxy tunnel to #{tunnel}" }
|
102
|
-
tunnel
|
103
|
-
end
|
104
|
-
|
105
|
-
def empty?
|
106
|
-
@requests.reject { |r| r.verb == :connect }.empty? || @requests.all? { |request| !request.response.nil? }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
97
|
class ConnectRequest < Request
|
111
98
|
def initialize(uri, _options)
|
112
99
|
super(:connect, uri, {})
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "resolv"
|
4
|
-
require "ipaddr"
|
5
|
-
require "forwardable"
|
6
|
-
|
7
3
|
module HTTPX
|
8
4
|
class HTTPProxyError < Error; end
|
9
5
|
|
@@ -85,7 +81,7 @@ module HTTPX
|
|
85
81
|
end
|
86
82
|
uris
|
87
83
|
end
|
88
|
-
|
84
|
+
{ uri: @_proxy_uris.first } unless @_proxy_uris.empty?
|
89
85
|
end
|
90
86
|
|
91
87
|
def find_connection(request, connections, options)
|
@@ -109,12 +105,15 @@ module HTTPX
|
|
109
105
|
return super unless proxy
|
110
106
|
|
111
107
|
connection = options.connection_class.new("tcp", uri, options)
|
112
|
-
|
113
|
-
|
108
|
+
catch(:coalesced) do
|
109
|
+
pool.init_connection(connection, options)
|
110
|
+
connection
|
111
|
+
end
|
114
112
|
end
|
115
113
|
|
116
114
|
def fetch_response(request, connections, options)
|
117
115
|
response = super
|
116
|
+
|
118
117
|
if response.is_a?(ErrorResponse) &&
|
119
118
|
__proxy_error?(response) && !@_proxy_uris.empty?
|
120
119
|
@_proxy_uris.shift
|
@@ -138,10 +137,20 @@ module HTTPX
|
|
138
137
|
error = response.error
|
139
138
|
case error
|
140
139
|
when NativeResolveError
|
140
|
+
return false unless @_proxy_uris && !@_proxy_uris.empty?
|
141
|
+
|
142
|
+
proxy_uri = URI(@_proxy_uris.first)
|
143
|
+
|
144
|
+
origin = error.connection.origin
|
145
|
+
|
141
146
|
# failed resolving proxy domain
|
142
|
-
|
147
|
+
origin.host == proxy_uri.host && origin.port == proxy_uri.port
|
143
148
|
when ResolveError
|
144
|
-
|
149
|
+
return false unless @_proxy_uris && !@_proxy_uris.empty?
|
150
|
+
|
151
|
+
proxy_uri = URI(@_proxy_uris.first)
|
152
|
+
|
153
|
+
error.message.end_with?(proxy_uri.to_s)
|
145
154
|
when *PROXY_ERRORS
|
146
155
|
# timeout errors connecting to proxy
|
147
156
|
true
|
@@ -160,7 +169,9 @@ module HTTPX
|
|
160
169
|
|
161
170
|
# redefining the connection origin as the proxy's URI,
|
162
171
|
# as this will be used as the tcp peer ip.
|
163
|
-
|
172
|
+
proxy_uri = URI(@options.proxy.uri)
|
173
|
+
@origin.host = proxy_uri.host
|
174
|
+
@origin.port = proxy_uri.port
|
164
175
|
end
|
165
176
|
|
166
177
|
def match?(uri, options)
|
@@ -169,11 +180,20 @@ module HTTPX
|
|
169
180
|
super && @options.proxy == options.proxy
|
170
181
|
end
|
171
182
|
|
172
|
-
|
173
|
-
def coalescable?(*)
|
183
|
+
def coalescable?(connection)
|
174
184
|
return super unless @options.proxy
|
175
185
|
|
176
|
-
|
186
|
+
if @io.protocol == "h2" &&
|
187
|
+
@origin.scheme == "https" &&
|
188
|
+
connection.origin.scheme == "https" &&
|
189
|
+
@io.can_verify_peer?
|
190
|
+
# in proxied connections, .origin is the proxy ; Given names
|
191
|
+
# are stored in .origins, this is what is used.
|
192
|
+
origin = URI(connection.origins.first)
|
193
|
+
@io.verify_hostname(origin.host)
|
194
|
+
else
|
195
|
+
@origin == connection.origin
|
196
|
+
end
|
177
197
|
end
|
178
198
|
|
179
199
|
def send(request)
|
@@ -222,13 +242,13 @@ module HTTPX
|
|
222
242
|
end
|
223
243
|
end
|
224
244
|
|
225
|
-
def
|
245
|
+
def handle_transition(nextstate)
|
226
246
|
return super unless @options.proxy
|
227
247
|
|
228
248
|
case nextstate
|
229
249
|
when :closing
|
230
250
|
# this is a hack so that we can use the super method
|
231
|
-
# and it'll
|
251
|
+
# and it'll think that the current state is open
|
232
252
|
@state = :open if @state == :connecting
|
233
253
|
end
|
234
254
|
super
|
@@ -12,16 +12,19 @@ module HTTPX
|
|
12
12
|
# TODO: pass max_retries in a configure/load block
|
13
13
|
|
14
14
|
IDEMPOTENT_METHODS = %i[get options head put delete].freeze
|
15
|
-
RETRYABLE_ERRORS = [
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
RETRYABLE_ERRORS = [
|
16
|
+
IOError,
|
17
|
+
EOFError,
|
18
|
+
Errno::ECONNRESET,
|
19
|
+
Errno::ECONNABORTED,
|
20
|
+
Errno::EPIPE,
|
21
|
+
Errno::EINVAL,
|
22
|
+
Errno::ETIMEDOUT,
|
23
|
+
Parser::Error,
|
24
|
+
TLSError,
|
25
|
+
TimeoutError,
|
26
|
+
Connection::HTTP2::GoawayError,
|
27
|
+
].freeze
|
25
28
|
DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
|
26
29
|
|
27
30
|
if ENV.key?("HTTPX_NO_JITTER")
|
@@ -38,7 +41,7 @@ module HTTPX
|
|
38
41
|
def option_retry_after(value)
|
39
42
|
# return early if callable
|
40
43
|
unless value.respond_to?(:call)
|
41
|
-
value =
|
44
|
+
value = Float(value)
|
42
45
|
raise TypeError, ":retry_after must be positive" unless value.positive?
|
43
46
|
end
|
44
47
|
|
@@ -93,8 +96,8 @@ module HTTPX
|
|
93
96
|
# rubocop:enable Style/MultilineTernaryOperator
|
94
97
|
)
|
95
98
|
response.close if response.respond_to?(:close)
|
96
|
-
request.retries -= 1
|
97
99
|
log { "failed to get response, #{request.retries} tries to go..." }
|
100
|
+
request.retries -= 1
|
98
101
|
request.transition(:idle)
|
99
102
|
|
100
103
|
retry_after = options.retry_after
|