httpx 0.18.0 → 0.19.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|