httpx 0.18.4 → 0.19.0
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/0_18_3.md +1 -1
- data/doc/release_notes/0_18_4.md +2 -2
- 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/lib/httpx/adapters/faraday.rb +7 -3
- data/lib/httpx/connection/http1.rb +6 -6
- data/lib/httpx/connection/http2.rb +7 -5
- data/lib/httpx/connection.rb +22 -10
- data/lib/httpx/extensions.rb +16 -0
- data/lib/httpx/headers.rb +0 -2
- data/lib/httpx/io/tcp.rb +27 -6
- 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 +7 -1
- 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 +1 -1
- data/lib/httpx/pool.rb +40 -20
- 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 +92 -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/session.rb +1 -15
- 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 +0 -2
- data/sig/connection/http2.rbs +2 -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 +15 -4
- data/lib/httpx/resolver/resolver_mixin.rb +0 -75
- data/sig/resolver/resolver_mixin.rbs +0 -26
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
|
@@ -96,8 +96,8 @@ module HTTPX
|
|
96
96
|
# rubocop:enable Style/MultilineTernaryOperator
|
97
97
|
)
|
98
98
|
response.close if response.respond_to?(:close)
|
99
|
-
request.retries -= 1
|
100
99
|
log { "failed to get response, #{request.retries} tries to go..." }
|
100
|
+
request.retries -= 1
|
101
101
|
request.transition(:idle)
|
102
102
|
|
103
103
|
retry_after = options.retry_after
|
data/lib/httpx/pool.rb
CHANGED
@@ -14,7 +14,6 @@ module HTTPX
|
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@resolvers = {}
|
17
|
-
@_resolver_ios = {}
|
18
17
|
@timers = Timers.new
|
19
18
|
@selector = Selector.new
|
20
19
|
@connections = []
|
@@ -56,9 +55,20 @@ module HTTPX
|
|
56
55
|
connections = connections.reject(&:inflight?)
|
57
56
|
connections.each(&:close)
|
58
57
|
next_tick until connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
58
|
+
|
59
|
+
# close resolvers
|
60
|
+
outstanding_connections = @connections
|
61
|
+
resolver_connections = @resolvers.each_value.flat_map(&:connections).compact
|
62
|
+
outstanding_connections -= resolver_connections
|
63
|
+
|
64
|
+
return unless outstanding_connections.empty?
|
65
|
+
|
59
66
|
@resolvers.each_value do |resolver|
|
60
67
|
resolver.close unless resolver.closed?
|
61
|
-
end
|
68
|
+
end
|
69
|
+
# for https resolver
|
70
|
+
resolver_connections.each(&:close)
|
71
|
+
next_tick until resolver_connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
62
72
|
end
|
63
73
|
|
64
74
|
def init_connection(connection, _options)
|
@@ -107,11 +117,12 @@ module HTTPX
|
|
107
117
|
return
|
108
118
|
end
|
109
119
|
|
110
|
-
|
111
|
-
|
112
|
-
|
120
|
+
find_resolver_for(connection) do |resolver|
|
121
|
+
resolver << connection
|
122
|
+
next if resolver.empty?
|
113
123
|
|
114
|
-
|
124
|
+
select_connection(resolver)
|
125
|
+
end
|
115
126
|
end
|
116
127
|
|
117
128
|
def on_resolver_connection(connection)
|
@@ -138,12 +149,11 @@ module HTTPX
|
|
138
149
|
|
139
150
|
def on_resolver_close(resolver)
|
140
151
|
resolver_type = resolver.class
|
141
|
-
return
|
152
|
+
return if resolver.closed?
|
142
153
|
|
143
154
|
@resolvers.delete(resolver_type)
|
144
155
|
|
145
156
|
deselect_connection(resolver)
|
146
|
-
@_resolver_ios.delete(resolver)
|
147
157
|
resolver.close unless resolver.closed?
|
148
158
|
end
|
149
159
|
|
@@ -174,12 +184,10 @@ module HTTPX
|
|
174
184
|
end
|
175
185
|
|
176
186
|
def coalesce_connections(conn1, conn2)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
register_connection(conn2)
|
182
|
-
end
|
187
|
+
return register_connection(conn2) unless conn1.coalescable?(conn2)
|
188
|
+
|
189
|
+
conn1.merge(conn2)
|
190
|
+
@connections.delete(conn2)
|
183
191
|
end
|
184
192
|
|
185
193
|
def next_timeout
|
@@ -196,13 +204,25 @@ module HTTPX
|
|
196
204
|
resolver_type = Resolver.registry(resolver_type) if resolver_type.is_a?(Symbol)
|
197
205
|
|
198
206
|
@resolvers[resolver_type] ||= begin
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
207
|
+
resolver_manager = if resolver_type.multi?
|
208
|
+
Resolver::Multi.new(resolver_type, connection_options)
|
209
|
+
else
|
210
|
+
resolver_type.new(connection_options)
|
211
|
+
end
|
212
|
+
resolver_manager.on(:resolve, &method(:on_resolver_connection))
|
213
|
+
resolver_manager.on(:error, &method(:on_resolver_error))
|
214
|
+
resolver_manager.on(:close, &method(:on_resolver_close))
|
215
|
+
resolver_manager
|
216
|
+
end
|
217
|
+
|
218
|
+
manager = @resolvers[resolver_type]
|
219
|
+
|
220
|
+
(manager.is_a?(Resolver::Multi) && manager.early_resolve(connection)) || manager.resolvers.each do |resolver|
|
221
|
+
resolver.pool = self
|
222
|
+
yield resolver
|
205
223
|
end
|
224
|
+
|
225
|
+
manager
|
206
226
|
end
|
207
227
|
end
|
208
228
|
end
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -6,32 +6,23 @@ require "cgi"
|
|
6
6
|
require "forwardable"
|
7
7
|
|
8
8
|
module HTTPX
|
9
|
-
class Resolver::HTTPS
|
9
|
+
class Resolver::HTTPS < Resolver::Resolver
|
10
10
|
extend Forwardable
|
11
|
-
include Resolver::ResolverMixin
|
12
11
|
using URIExtensions
|
12
|
+
using StringExtensions
|
13
13
|
|
14
14
|
NAMESERVER = "https://1.1.1.1/dns-query"
|
15
15
|
|
16
|
-
RECORD_TYPES = {
|
17
|
-
"A" => Resolv::DNS::Resource::IN::A,
|
18
|
-
"AAAA" => Resolv::DNS::Resource::IN::AAAA,
|
19
|
-
}.freeze
|
20
|
-
|
21
16
|
DEFAULTS = {
|
22
17
|
uri: NAMESERVER,
|
23
18
|
use_get: false,
|
24
|
-
record_types: RECORD_TYPES.keys,
|
25
19
|
}.freeze
|
26
20
|
|
27
21
|
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
def initialize(options)
|
32
|
-
@options = Options.new(options)
|
23
|
+
def initialize(_, options)
|
24
|
+
super
|
33
25
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
34
|
-
@_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
|
35
26
|
@queries = {}
|
36
27
|
@requests = {}
|
37
28
|
@connections = []
|
@@ -44,7 +35,7 @@ module HTTPX
|
|
44
35
|
def <<(connection)
|
45
36
|
return if @uri.origin == connection.origin.to_s
|
46
37
|
|
47
|
-
@uri_addresses ||=
|
38
|
+
@uri_addresses ||= HTTPX::Resolver.nolookup_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
48
39
|
|
49
40
|
if @uri_addresses.empty?
|
50
41
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
@@ -52,7 +43,7 @@ module HTTPX
|
|
52
43
|
throw(:resolve_error, ex)
|
53
44
|
end
|
54
45
|
|
55
|
-
|
46
|
+
resolve(connection)
|
56
47
|
end
|
57
48
|
|
58
49
|
def closed?
|
@@ -63,21 +54,22 @@ module HTTPX
|
|
63
54
|
true
|
64
55
|
end
|
65
56
|
|
66
|
-
private
|
67
|
-
|
68
57
|
def resolver_connection
|
69
58
|
@resolver_connection ||= @pool.find_connection(@uri, @options) || begin
|
70
59
|
@building_connection = true
|
71
60
|
connection = @options.connection_class.new("ssl", @uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
|
72
61
|
@pool.init_connection(connection, @options)
|
73
|
-
emit_addresses(connection, @uri_addresses)
|
62
|
+
emit_addresses(connection, @family, @uri_addresses)
|
74
63
|
@building_connection = false
|
75
64
|
connection
|
76
65
|
end
|
77
66
|
end
|
78
67
|
|
68
|
+
private
|
69
|
+
|
79
70
|
def resolve(connection = @connections.first, hostname = nil)
|
80
71
|
return if @building_connection
|
72
|
+
return unless connection
|
81
73
|
|
82
74
|
hostname ||= @queries.key(connection)
|
83
75
|
|
@@ -85,17 +77,16 @@ module HTTPX
|
|
85
77
|
hostname = connection.origin.host
|
86
78
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
87
79
|
end
|
88
|
-
|
89
|
-
log { "resolver: query #{type} for #{hostname}" }
|
80
|
+
log { "resolver: query #{FAMILY_TYPES[RECORD_TYPES[@family]]} for #{hostname}" }
|
90
81
|
begin
|
91
|
-
request = build_request(hostname
|
82
|
+
request = build_request(hostname)
|
92
83
|
request.on(:response, &method(:on_response).curry(2)[request])
|
93
84
|
request.on(:promise, &method(:on_promise))
|
94
85
|
@requests[request] = connection
|
95
86
|
resolver_connection.send(request)
|
96
87
|
@queries[hostname] = connection
|
97
88
|
@connections << connection
|
98
|
-
rescue Resolv::DNS::EncodeError, JSON::JSONError => e
|
89
|
+
rescue ResolveError, Resolv::DNS::EncodeError, JSON::JSONError => e
|
99
90
|
emit_resolve_error(connection, hostname, e)
|
100
91
|
end
|
101
92
|
end
|
@@ -107,6 +98,7 @@ module HTTPX
|
|
107
98
|
hostname = @queries.key(connection)
|
108
99
|
emit_resolve_error(connection, hostname, e)
|
109
100
|
else
|
101
|
+
# @type var response: HTTPX::Response
|
110
102
|
parse(response)
|
111
103
|
ensure
|
112
104
|
@requests.delete(request)
|
@@ -122,21 +114,15 @@ module HTTPX
|
|
122
114
|
answers = decode_response_body(response)
|
123
115
|
rescue Resolv::DNS::DecodeError, JSON::JSONError => e
|
124
116
|
host, connection = @queries.first
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
return
|
129
|
-
end
|
117
|
+
@queries.delete(host)
|
118
|
+
emit_resolve_error(connection, host, e)
|
119
|
+
return
|
130
120
|
end
|
131
121
|
if answers.nil? || answers.empty?
|
132
122
|
host, connection = @queries.first
|
133
|
-
@
|
134
|
-
|
135
|
-
|
136
|
-
@_record_types.delete(host)
|
137
|
-
emit_resolve_error(connection, host)
|
138
|
-
return
|
139
|
-
end
|
123
|
+
@queries.delete(host)
|
124
|
+
emit_resolve_error(connection, host)
|
125
|
+
return
|
140
126
|
else
|
141
127
|
answers = answers.group_by { |answer| answer["name"] }
|
142
128
|
answers.each do |hostname, addresses|
|
@@ -146,8 +132,12 @@ module HTTPX
|
|
146
132
|
if alias_address.nil?
|
147
133
|
connection = @queries[hostname]
|
148
134
|
@queries.delete(address["name"])
|
149
|
-
|
150
|
-
|
135
|
+
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
|
136
|
+
@connections.delete(connection)
|
137
|
+
else
|
138
|
+
resolve(connection, address["alias"])
|
139
|
+
return # rubocop:disable Lint/NonLocalExitFromIterator
|
140
|
+
end
|
151
141
|
else
|
152
142
|
alias_address
|
153
143
|
end
|
@@ -157,13 +147,13 @@ module HTTPX
|
|
157
147
|
end.compact
|
158
148
|
next if addresses.empty?
|
159
149
|
|
160
|
-
hostname
|
150
|
+
hostname.delete_suffix!(".") if hostname.end_with?(".")
|
161
151
|
connection = @queries.delete(hostname)
|
162
152
|
next unless connection # probably a retried query for which there's an answer
|
163
153
|
|
164
154
|
@connections.delete(connection)
|
165
|
-
Resolver.cached_lookup_set(hostname, addresses) if @resolver_options[:cache]
|
166
|
-
emit_addresses(connection, addresses.map { |addr| addr["data"] })
|
155
|
+
Resolver.cached_lookup_set(hostname, @family, addresses) if @resolver_options[:cache]
|
156
|
+
emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
|
167
157
|
end
|
168
158
|
end
|
169
159
|
return if @connections.empty?
|
@@ -171,14 +161,14 @@ module HTTPX
|
|
171
161
|
resolve
|
172
162
|
end
|
173
163
|
|
174
|
-
def build_request(hostname
|
164
|
+
def build_request(hostname)
|
175
165
|
uri = @uri.dup
|
176
166
|
rklass = @options.request_class
|
177
|
-
payload = Resolver.encode_dns_query(hostname, type:
|
167
|
+
payload = Resolver.encode_dns_query(hostname, type: @record_type)
|
178
168
|
|
179
169
|
if @resolver_options[:use_get]
|
180
170
|
params = URI.decode_www_form(uri.query.to_s)
|
181
|
-
params << ["type",
|
171
|
+
params << ["type", FAMILY_TYPES[@record_type]]
|
182
172
|
params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
|
183
173
|
uri.query = URI.encode_www_form(params)
|
184
174
|
request = rklass.new("GET", uri, @options)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require "resolv"
|
5
|
+
|
6
|
+
module HTTPX
|
7
|
+
class Resolver::Multi
|
8
|
+
include Callbacks
|
9
|
+
using ArrayExtensions
|
10
|
+
|
11
|
+
attr_reader :resolvers
|
12
|
+
|
13
|
+
def initialize(resolver_type, options)
|
14
|
+
@options = options
|
15
|
+
@resolver_options = @options.resolver_options
|
16
|
+
|
17
|
+
@resolvers = options.ip_families.map do |ip_family|
|
18
|
+
resolver = resolver_type.new(ip_family, options)
|
19
|
+
resolver.on(:resolve, &method(:on_resolver_connection))
|
20
|
+
resolver.on(:error, &method(:on_resolver_error))
|
21
|
+
resolver.on(:close) { on_resolver_close(resolver) }
|
22
|
+
resolver
|
23
|
+
end
|
24
|
+
|
25
|
+
@errors = Hash.new { |hs, k| hs[k] = [] }
|
26
|
+
end
|
27
|
+
|
28
|
+
def closed?
|
29
|
+
@resolvers.all?(&:closed?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def timeout
|
33
|
+
@resolvers.map(&:timeout).min
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
@resolvers.each(&:close)
|
38
|
+
end
|
39
|
+
|
40
|
+
def connections
|
41
|
+
@resolvers.filter_map { |r| r.resolver_connection if r.respond_to?(:resolver_connection) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def early_resolve(connection)
|
45
|
+
hostname = connection.origin.host
|
46
|
+
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
47
|
+
return unless addresses
|
48
|
+
|
49
|
+
addresses = addresses.group_by(&:family)
|
50
|
+
|
51
|
+
@resolvers.each do |resolver|
|
52
|
+
addrs = addresses[resolver.family]
|
53
|
+
|
54
|
+
next if !addrs || addrs.empty?
|
55
|
+
|
56
|
+
resolver.emit_addresses(connection, resolver.family, addrs)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def on_resolver_connection(connection)
|
63
|
+
emit(:resolve, connection)
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_resolver_error(connection, error)
|
67
|
+
@errors[connection] << error
|
68
|
+
|
69
|
+
return unless @errors[connection].size >= @resolvers.size
|
70
|
+
|
71
|
+
errors = @errors.delete(connection)
|
72
|
+
emit(:error, connection, errors.first)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_resolver_close(resolver)
|
76
|
+
emit(:close, resolver)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -4,22 +4,15 @@ require "forwardable"
|
|
4
4
|
require "resolv"
|
5
5
|
|
6
6
|
module HTTPX
|
7
|
-
class Resolver::Native
|
7
|
+
class Resolver::Native < Resolver::Resolver
|
8
8
|
extend Forwardable
|
9
|
-
include Resolver::ResolverMixin
|
10
9
|
using URIExtensions
|
11
10
|
|
12
|
-
RECORD_TYPES = {
|
13
|
-
"A" => Resolv::DNS::Resource::IN::A,
|
14
|
-
"AAAA" => Resolv::DNS::Resource::IN::AAAA,
|
15
|
-
}.freeze
|
16
|
-
|
17
11
|
DEFAULTS = if RUBY_VERSION < "2.2"
|
18
12
|
{
|
19
13
|
**Resolv::DNS::Config.default_config_hash,
|
20
14
|
packet_size: 512,
|
21
15
|
timeouts: Resolver::RESOLVE_TIMEOUT,
|
22
|
-
record_types: RECORD_TYPES.keys,
|
23
16
|
}.freeze
|
24
17
|
else
|
25
18
|
{
|
@@ -27,7 +20,6 @@ module HTTPX
|
|
27
20
|
**Resolv::DNS::Config.default_config_hash,
|
28
21
|
packet_size: 512,
|
29
22
|
timeouts: Resolver::RESOLVE_TIMEOUT,
|
30
|
-
record_types: RECORD_TYPES.keys,
|
31
23
|
}.freeze
|
32
24
|
end
|
33
25
|
|
@@ -49,14 +41,13 @@ module HTTPX
|
|
49
41
|
|
50
42
|
attr_reader :state
|
51
43
|
|
52
|
-
def initialize(options)
|
53
|
-
|
44
|
+
def initialize(_, options)
|
45
|
+
super
|
54
46
|
@ns_index = 0
|
55
47
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
56
48
|
@nameserver = @resolver_options[:nameserver]
|
57
49
|
@_timeouts = Array(@resolver_options[:timeouts])
|
58
50
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
59
|
-
@_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
|
60
51
|
@connections = []
|
61
52
|
@queries = {}
|
62
53
|
@read_buffer = "".b
|
@@ -107,8 +98,6 @@ module HTTPX
|
|
107
98
|
end
|
108
99
|
|
109
100
|
def <<(connection)
|
110
|
-
return if early_resolve(connection)
|
111
|
-
|
112
101
|
if @nameserver.nil?
|
113
102
|
ex = ResolveError.new("No available nameserver")
|
114
103
|
ex.set_backtrace(caller)
|
@@ -140,7 +129,7 @@ module HTTPX
|
|
140
129
|
end
|
141
130
|
|
142
131
|
def do_retry
|
143
|
-
return if @queries.empty?
|
132
|
+
return if @queries.empty? || !@start_timeout
|
144
133
|
|
145
134
|
loop_time = Utils.elapsed_time(@start_timeout)
|
146
135
|
connections = []
|
@@ -160,7 +149,7 @@ module HTTPX
|
|
160
149
|
@connections.delete(connection)
|
161
150
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
162
151
|
# resolve timeout, including from the previous retries.
|
163
|
-
raise ResolveTimeoutError.new(loop_time, "Timed out")
|
152
|
+
raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{host}")
|
164
153
|
# raise NativeResolveError.new(connection, host)
|
165
154
|
else
|
166
155
|
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
@@ -198,25 +187,21 @@ module HTTPX
|
|
198
187
|
addresses = Resolver.decode_dns_answer(buffer)
|
199
188
|
rescue Resolv::DNS::DecodeError => e
|
200
189
|
hostname, connection = @queries.first
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
end
|
190
|
+
@queries.delete(hostname)
|
191
|
+
@timeouts.delete(hostname)
|
192
|
+
@connections.delete(connection)
|
193
|
+
ex = NativeResolveError.new(connection, hostname, e.message)
|
194
|
+
ex.set_backtrace(e.backtrace)
|
195
|
+
raise ex
|
208
196
|
end
|
209
197
|
|
210
198
|
if addresses.nil? || addresses.empty?
|
211
199
|
hostname, connection = @queries.first
|
212
|
-
@
|
213
|
-
|
214
|
-
|
215
|
-
@_record_types.delete(hostname)
|
216
|
-
@connections.delete(connection)
|
200
|
+
@queries.delete(hostname)
|
201
|
+
@timeouts.delete(hostname)
|
202
|
+
@connections.delete(connection)
|
217
203
|
|
218
|
-
|
219
|
-
end
|
204
|
+
raise NativeResolveError.new(connection, hostname)
|
220
205
|
else
|
221
206
|
address = addresses.first
|
222
207
|
name = address["name"]
|
@@ -236,16 +221,21 @@ module HTTPX
|
|
236
221
|
end
|
237
222
|
|
238
223
|
if address.key?("alias") # CNAME
|
239
|
-
|
224
|
+
# clean up intermediate queries
|
225
|
+
@timeouts.delete(name) unless connection.origin.host == name
|
226
|
+
|
227
|
+
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
|
240
228
|
@connections.delete(connection)
|
241
229
|
else
|
242
230
|
resolve(connection, address["alias"])
|
243
231
|
return
|
244
232
|
end
|
245
233
|
else
|
234
|
+
@timeouts.delete(name)
|
235
|
+
@timeouts.delete(connection.origin.host)
|
246
236
|
@connections.delete(connection)
|
247
|
-
Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options[:cache]
|
248
|
-
emit_addresses(connection, addresses.map { |addr| addr["data"] })
|
237
|
+
Resolver.cached_lookup_set(connection.origin.host, @family, addresses) if @resolver_options[:cache]
|
238
|
+
emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
|
249
239
|
end
|
250
240
|
end
|
251
241
|
return emit(:close) if @connections.empty?
|
@@ -264,10 +254,9 @@ module HTTPX
|
|
264
254
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
265
255
|
end
|
266
256
|
@queries[hostname] = connection
|
267
|
-
|
268
|
-
log { "resolver: query #{type} for #{hostname}" }
|
257
|
+
log { "resolver: query #{@record_type.name.split("::").last} for #{hostname}" }
|
269
258
|
begin
|
270
|
-
@write_buffer << Resolver.encode_dns_query(hostname, type:
|
259
|
+
@write_buffer << Resolver.encode_dns_query(hostname, type: @record_type)
|
271
260
|
rescue Resolv::DNS::EncodeError => e
|
272
261
|
emit_resolve_error(connection, hostname, e)
|
273
262
|
end
|
@@ -306,6 +295,9 @@ module HTTPX
|
|
306
295
|
return unless @state == :open
|
307
296
|
|
308
297
|
@io.close if @io
|
298
|
+
@start_timeout = nil
|
299
|
+
@write_buffer.clear
|
300
|
+
@read_buffer.clear
|
309
301
|
end
|
310
302
|
@state = nextstate
|
311
303
|
end
|