httpx 1.6.2 → 1.7.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_11_0.md +3 -3
- data/doc/release_notes/1_6_3.md +47 -0
- data/doc/release_notes/1_7_0.md +149 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/sentry.rb +1 -1
- data/lib/httpx/altsvc.rb +3 -1
- data/lib/httpx/connection/http1.rb +14 -15
- data/lib/httpx/connection/http2.rb +16 -15
- data/lib/httpx/connection.rb +118 -110
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/extensions.rb +0 -14
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +1 -1
- data/lib/httpx/loggable.rb +14 -2
- data/lib/httpx/options.rb +60 -17
- data/lib/httpx/plugins/auth/digest.rb +44 -4
- data/lib/httpx/plugins/auth.rb +87 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +0 -1
- data/lib/httpx/plugins/callbacks.rb +15 -1
- data/lib/httpx/plugins/cookies/cookie.rb +1 -0
- data/lib/httpx/plugins/digest_auth.rb +4 -5
- data/lib/httpx/plugins/fiber_concurrency.rb +16 -1
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +162 -56
- data/lib/httpx/plugins/proxy/http.rb +37 -9
- data/lib/httpx/plugins/rate_limiter.rb +2 -2
- data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
- data/lib/httpx/plugins/response_cache.rb +16 -9
- data/lib/httpx/plugins/retries.rb +55 -16
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +59 -8
- data/lib/httpx/plugins/stream_bidi.rb +87 -22
- data/lib/httpx/pool.rb +65 -21
- data/lib/httpx/request.rb +13 -14
- data/lib/httpx/resolver/https.rb +100 -34
- data/lib/httpx/resolver/multi.rb +12 -27
- data/lib/httpx/resolver/native.rb +68 -38
- data/lib/httpx/resolver/resolver.rb +46 -29
- data/lib/httpx/resolver/system.rb +63 -39
- data/lib/httpx/resolver.rb +97 -29
- data/lib/httpx/response/body.rb +2 -0
- data/lib/httpx/response.rb +22 -6
- data/lib/httpx/selector.rb +44 -20
- data/lib/httpx/session.rb +23 -33
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/deflate.rb +13 -8
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/multipart/decoder.rb +4 -4
- data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +16 -8
- data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +3 -0
- data/sig/chainable.rbs +3 -3
- data/sig/connection.rbs +13 -6
- data/sig/loggable.rbs +5 -1
- data/sig/options.rbs +6 -2
- data/sig/plugins/auth/digest.rbs +6 -0
- data/sig/plugins/auth.rbs +28 -4
- data/sig/plugins/basic_auth.rbs +3 -3
- data/sig/plugins/callbacks.rbs +3 -0
- data/sig/plugins/digest_auth.rbs +2 -4
- data/sig/plugins/fiber_concurrency.rbs +6 -0
- data/sig/plugins/ntlm_auth.rbs +2 -2
- data/sig/plugins/oauth.rbs +46 -15
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache/file_store.rbs +2 -0
- data/sig/plugins/response_cache.rbs +4 -0
- data/sig/plugins/retries.rbs +8 -2
- data/sig/plugins/stream.rbs +13 -3
- data/sig/plugins/stream_bidi.rbs +5 -7
- data/sig/pool.rbs +1 -1
- data/sig/resolver/https.rbs +7 -0
- data/sig/resolver/multi.rbs +2 -9
- data/sig/resolver/native.rbs +1 -1
- data/sig/resolver/resolver.rbs +9 -8
- data/sig/resolver/system.rbs +4 -2
- data/sig/resolver.rbs +12 -3
- data/sig/response.rbs +3 -0
- data/sig/selector.rbs +2 -0
- data/sig/session.rbs +8 -8
- data/sig/transcoder/multipart.rbs +4 -2
- data/sig/transcoder.rbs +5 -1
- metadata +5 -1
data/lib/httpx/pool.rb
CHANGED
|
@@ -8,7 +8,6 @@ require "httpx/resolver"
|
|
|
8
8
|
|
|
9
9
|
module HTTPX
|
|
10
10
|
class Pool
|
|
11
|
-
using ArrayExtensions::FilterMap
|
|
12
11
|
using URIExtensions
|
|
13
12
|
|
|
14
13
|
POOL_TIMEOUT = 5
|
|
@@ -51,32 +50,53 @@ module HTTPX
|
|
|
51
50
|
acquire_connection(uri, options) || begin
|
|
52
51
|
if @connections_counter == @max_connections
|
|
53
52
|
# this takes precedence over per-origin
|
|
54
|
-
@max_connections_cond.wait(@connection_mtx, @pool_timeout)
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
return conn
|
|
58
|
-
end
|
|
54
|
+
expires_at = Utils.now + @pool_timeout
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# this means that all of them are persistent or being used, so raise a timeout error.
|
|
63
|
-
conn = @connections.find { |c| c.state == :closed }
|
|
56
|
+
loop do
|
|
57
|
+
@max_connections_cond.wait(@connection_mtx, @pool_timeout)
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
if (conn = acquire_connection(uri, options))
|
|
60
|
+
return conn
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# if one can afford to create a new connection, do it
|
|
64
|
+
break unless @connections_counter == @max_connections
|
|
65
|
+
|
|
66
|
+
# if no matching usable connection was found, the pool will make room and drop a closed connection.
|
|
67
|
+
if (conn = @connections.find { |c| c.state == :closed })
|
|
68
|
+
drop_connection(conn)
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# happens when a condition was signalled, but another thread snatched the available connection before
|
|
73
|
+
# context was passed back here.
|
|
74
|
+
next if Utils.now < expires_at
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
raise PoolTimeoutError.new(@pool_timeout,
|
|
77
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection")
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
if @origin_counters[uri.origin] == @max_connections_per_origin
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
expires_at = Utils.now + @pool_timeout
|
|
85
|
+
|
|
86
|
+
loop do
|
|
87
|
+
@origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
|
|
88
|
+
|
|
89
|
+
if (conn = acquire_connection(uri, options))
|
|
90
|
+
return conn
|
|
91
|
+
end
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
# happens when a condition was signalled, but another thread snatched the available connection before
|
|
94
|
+
# context was passed back here.
|
|
95
|
+
next if Utils.now < expires_at
|
|
96
|
+
|
|
97
|
+
raise(PoolTimeoutError.new(@pool_timeout,
|
|
98
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection to #{uri.origin}"))
|
|
99
|
+
end
|
|
80
100
|
end
|
|
81
101
|
|
|
82
102
|
@connections_counter += 1
|
|
@@ -91,10 +111,23 @@ module HTTPX
|
|
|
91
111
|
return if connection.options.io
|
|
92
112
|
|
|
93
113
|
@connection_mtx.synchronize do
|
|
114
|
+
if connection.coalesced? || connection.state == :idle
|
|
115
|
+
# when connections coalesce
|
|
116
|
+
drop_connection(connection)
|
|
117
|
+
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
94
121
|
@connections << connection
|
|
95
122
|
|
|
96
123
|
@max_connections_cond.signal
|
|
97
124
|
@origin_conds[connection.origin.to_s].signal
|
|
125
|
+
|
|
126
|
+
# Observed situations where a session handling multiple requests in a loop
|
|
127
|
+
# across multiple threads checks the same connection in and out, while another
|
|
128
|
+
# thread which is waiting on the same connection never gets the chance to pick
|
|
129
|
+
# it up, because ruby's thread scheduler never switched on to it in the process.
|
|
130
|
+
Thread.pass
|
|
98
131
|
end
|
|
99
132
|
end
|
|
100
133
|
|
|
@@ -128,10 +161,16 @@ module HTTPX
|
|
|
128
161
|
end
|
|
129
162
|
|
|
130
163
|
def checkin_resolver(resolver)
|
|
131
|
-
|
|
132
|
-
resolvers = @resolvers[resolver.class]
|
|
164
|
+
resolver_class = resolver.class
|
|
133
165
|
|
|
134
|
-
|
|
166
|
+
resolver = resolver.multi
|
|
167
|
+
|
|
168
|
+
# a multi requires all sub-resolvers being closed in order to be
|
|
169
|
+
# correctly checked back in.
|
|
170
|
+
return unless resolver.closed?
|
|
171
|
+
|
|
172
|
+
@resolver_mtx.synchronize do
|
|
173
|
+
resolvers = @resolvers[resolver_class]
|
|
135
174
|
|
|
136
175
|
resolvers << resolver unless resolvers.include?(resolver)
|
|
137
176
|
end
|
|
@@ -140,6 +179,7 @@ module HTTPX
|
|
|
140
179
|
# :nocov:
|
|
141
180
|
def inspect
|
|
142
181
|
"#<#{self.class}:#{object_id} " \
|
|
182
|
+
"@max_connections=#{@max_connections} " \
|
|
143
183
|
"@max_connections_per_origin=#{@max_connections_per_origin} " \
|
|
144
184
|
"@pool_timeout=#{@pool_timeout} " \
|
|
145
185
|
"@connections=#{@connections.size}>"
|
|
@@ -159,15 +199,19 @@ module HTTPX
|
|
|
159
199
|
end
|
|
160
200
|
|
|
161
201
|
def checkout_new_connection(uri, options)
|
|
162
|
-
options.connection_class.new(uri, options)
|
|
202
|
+
connection = options.connection_class.new(uri, options)
|
|
203
|
+
connection.log(level: 2) { "created connection##{connection.object_id} in pool##{object_id}" }
|
|
204
|
+
connection
|
|
163
205
|
end
|
|
164
206
|
|
|
165
207
|
def checkout_new_resolver(resolver_type, options)
|
|
166
|
-
if resolver_type.multi?
|
|
208
|
+
resolver = if resolver_type.multi?
|
|
167
209
|
Resolver::Multi.new(resolver_type, options)
|
|
168
210
|
else
|
|
169
211
|
resolver_type.new(options)
|
|
170
212
|
end
|
|
213
|
+
resolver.log(level: 2) { "created resolver##{resolver.object_id} in pool##{object_id}" }
|
|
214
|
+
resolver
|
|
171
215
|
end
|
|
172
216
|
|
|
173
217
|
# drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
|
data/lib/httpx/request.rb
CHANGED
|
@@ -10,13 +10,11 @@ module HTTPX
|
|
|
10
10
|
extend Forwardable
|
|
11
11
|
include Loggable
|
|
12
12
|
include Callbacks
|
|
13
|
+
|
|
13
14
|
using URIExtensions
|
|
14
15
|
|
|
15
16
|
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
|
16
17
|
|
|
17
|
-
# default value used for "user-agent" header, when not overridden.
|
|
18
|
-
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
19
|
-
|
|
20
18
|
# the upcased string HTTP verb for this request.
|
|
21
19
|
attr_reader :verb
|
|
22
20
|
|
|
@@ -75,16 +73,6 @@ module HTTPX
|
|
|
75
73
|
@headers = options.headers.dup
|
|
76
74
|
merge_headers(params.delete(:headers)) if params.key?(:headers)
|
|
77
75
|
|
|
78
|
-
@headers["user-agent"] ||= USER_AGENT
|
|
79
|
-
@headers["accept"] ||= "*/*"
|
|
80
|
-
|
|
81
|
-
# forego compression in the Range request case
|
|
82
|
-
if @headers.key?("range")
|
|
83
|
-
@headers.delete("accept-encoding")
|
|
84
|
-
else
|
|
85
|
-
@headers["accept-encoding"] ||= options.supported_compression_formats
|
|
86
|
-
end
|
|
87
|
-
|
|
88
76
|
@query_params = params.delete(:params) if params.key?(:params)
|
|
89
77
|
|
|
90
78
|
@body = options.request_body_class.new(@headers, options, **params)
|
|
@@ -103,12 +91,20 @@ module HTTPX
|
|
|
103
91
|
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
|
104
92
|
|
|
105
93
|
@state = :idle
|
|
106
|
-
@response = @peer_address = @
|
|
94
|
+
@response = @peer_address = @informational_status = nil
|
|
107
95
|
@ping = false
|
|
108
96
|
@persistent = @options.persistent
|
|
109
97
|
@active_timeouts = []
|
|
110
98
|
end
|
|
111
99
|
|
|
100
|
+
# dupped initialization
|
|
101
|
+
def initialize_dup(orig)
|
|
102
|
+
super
|
|
103
|
+
@uri = orig.instance_variable_get(:@uri).dup
|
|
104
|
+
@headers = orig.instance_variable_get(:@headers).dup
|
|
105
|
+
@body = orig.instance_variable_get(:@body).dup
|
|
106
|
+
end
|
|
107
|
+
|
|
112
108
|
def complete!(response = @response)
|
|
113
109
|
emit(:complete, response)
|
|
114
110
|
end
|
|
@@ -166,6 +162,9 @@ module HTTPX
|
|
|
166
162
|
# merges +h+ into the instance of HTTPX::Headers of the request.
|
|
167
163
|
def merge_headers(h)
|
|
168
164
|
@headers = @headers.merge(h)
|
|
165
|
+
return unless @headers.key?("range")
|
|
166
|
+
|
|
167
|
+
@headers.delete("accept-encoding")
|
|
169
168
|
end
|
|
170
169
|
|
|
171
170
|
# the URI scheme of the request +uri+.
|
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -12,6 +12,7 @@ module HTTPX
|
|
|
12
12
|
#
|
|
13
13
|
class Resolver::HTTPS < Resolver::Resolver
|
|
14
14
|
extend Forwardable
|
|
15
|
+
|
|
15
16
|
using URIExtensions
|
|
16
17
|
|
|
17
18
|
module DNSExtensions
|
|
@@ -30,17 +31,20 @@ module HTTPX
|
|
|
30
31
|
use_get: false,
|
|
31
32
|
}.freeze
|
|
32
33
|
|
|
33
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close,
|
|
34
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close,
|
|
35
|
+
:closed?, :deactivate, :terminate, :inflight?, :handle_socket_timeout
|
|
34
36
|
|
|
35
37
|
def initialize(_, options)
|
|
36
38
|
super
|
|
37
39
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
|
38
40
|
@queries = {}
|
|
39
41
|
@requests = {}
|
|
42
|
+
@_timeouts = Array(@resolver_options[:timeouts])
|
|
43
|
+
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
|
40
44
|
@uri = URI(@resolver_options[:uri])
|
|
41
|
-
@uri_addresses = nil
|
|
45
|
+
@name = @uri_addresses = nil
|
|
42
46
|
@resolver = Resolv::DNS.new
|
|
43
|
-
@resolver.timeouts = @
|
|
47
|
+
@resolver.timeouts = @_timeouts.empty? ? Resolver::RESOLVE_TIMEOUT : @_timeouts
|
|
44
48
|
@resolver.lazy_initialize
|
|
45
49
|
end
|
|
46
50
|
|
|
@@ -52,29 +56,24 @@ module HTTPX
|
|
|
52
56
|
if @uri_addresses.empty?
|
|
53
57
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
|
54
58
|
ex.set_backtrace(caller)
|
|
55
|
-
connection.
|
|
59
|
+
connection.force_close
|
|
56
60
|
throw(:resolve_error, ex)
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
resolve(connection)
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
# This is already indirectly monitored bt the HTTP connection. In order to skip
|
|
63
|
-
# monitoring, this method returns <tt>true</tt>.
|
|
64
|
-
def closed?
|
|
65
|
-
true
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def empty?
|
|
69
|
-
true
|
|
70
|
-
end
|
|
71
|
-
|
|
72
66
|
def resolver_connection
|
|
73
67
|
# TODO: leaks connection object into the pool
|
|
74
|
-
@resolver_connection ||=
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
@resolver_connection ||=
|
|
69
|
+
@current_session.find_connection(
|
|
70
|
+
@uri,
|
|
71
|
+
@current_selector,
|
|
72
|
+
@options.merge(resolver_class: :system, ssl: { alpn_protocols: %w[h2] })
|
|
73
|
+
).tap do |conn|
|
|
74
|
+
emit_addresses(conn, @family, @uri_addresses) unless conn.addresses
|
|
75
|
+
conn.on(:force_closed, &method(:force_close))
|
|
76
|
+
end
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
private
|
|
@@ -100,19 +99,26 @@ module HTTPX
|
|
|
100
99
|
else
|
|
101
100
|
@queries[hostname] = connection
|
|
102
101
|
end
|
|
102
|
+
|
|
103
|
+
@name = hostname
|
|
104
|
+
|
|
103
105
|
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
send_request(hostname, connection)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def send_request(hostname, connection)
|
|
111
|
+
request = build_request(hostname)
|
|
112
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
|
113
|
+
request.on(:promise, &method(:on_promise))
|
|
114
|
+
@requests[request] = hostname
|
|
115
|
+
resolver_connection.send(request)
|
|
116
|
+
@connections << connection
|
|
117
|
+
rescue ResolveError, Resolv::DNS::EncodeError => e
|
|
118
|
+
reset_hostname(hostname)
|
|
119
|
+
throw(:resolve_error, e) if connection.pending.empty?
|
|
120
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
|
121
|
+
close_or_resolve
|
|
116
122
|
end
|
|
117
123
|
|
|
118
124
|
def on_response(request, response)
|
|
@@ -121,8 +127,21 @@ module HTTPX
|
|
|
121
127
|
hostname = @requests.delete(request)
|
|
122
128
|
connection = reset_hostname(hostname)
|
|
123
129
|
emit_resolve_error(connection, connection.peer.host, e)
|
|
130
|
+
close_or_resolve
|
|
124
131
|
else
|
|
125
132
|
# @type var response: HTTPX::Response
|
|
133
|
+
if response.status.between?(300, 399) && response.headers.key?("location")
|
|
134
|
+
hostname = @requests[request]
|
|
135
|
+
connection = @queries[hostname]
|
|
136
|
+
location_uri = URI(response.headers["location"])
|
|
137
|
+
location_uri = response.uri.merge(location_uri) if location_uri.relative?
|
|
138
|
+
|
|
139
|
+
# we assume that the DNS server URI changed permanently and move on
|
|
140
|
+
@uri = location_uri
|
|
141
|
+
send_request(hostname, connection)
|
|
142
|
+
return
|
|
143
|
+
end
|
|
144
|
+
|
|
126
145
|
parse(request, response)
|
|
127
146
|
ensure
|
|
128
147
|
@requests.delete(request)
|
|
@@ -134,6 +153,10 @@ module HTTPX
|
|
|
134
153
|
end
|
|
135
154
|
|
|
136
155
|
def parse(request, response)
|
|
156
|
+
hostname = @name
|
|
157
|
+
|
|
158
|
+
@name = nil
|
|
159
|
+
|
|
137
160
|
code, result = decode_response_body(response)
|
|
138
161
|
|
|
139
162
|
case code
|
|
@@ -147,19 +170,39 @@ module HTTPX
|
|
|
147
170
|
|
|
148
171
|
unless @queries.value?(connection)
|
|
149
172
|
emit_resolve_error(connection)
|
|
173
|
+
close_or_resolve
|
|
150
174
|
return
|
|
151
175
|
end
|
|
152
176
|
|
|
153
177
|
resolve
|
|
178
|
+
when :retriable_error
|
|
179
|
+
timeouts = @timeouts[hostname]
|
|
180
|
+
|
|
181
|
+
unless timeouts.empty?
|
|
182
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: failed, but will retry..." }
|
|
183
|
+
|
|
184
|
+
connection = @queries[hostname]
|
|
185
|
+
|
|
186
|
+
resolve(connection, hostname)
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
host = @requests.delete(request)
|
|
191
|
+
connection = reset_hostname(host)
|
|
192
|
+
|
|
193
|
+
emit_resolve_error(connection)
|
|
194
|
+
close_or_resolve
|
|
154
195
|
when :dns_error
|
|
155
196
|
host = @requests.delete(request)
|
|
156
197
|
connection = reset_hostname(host)
|
|
157
198
|
|
|
158
199
|
emit_resolve_error(connection)
|
|
200
|
+
close_or_resolve
|
|
159
201
|
when :decode_error
|
|
160
202
|
host = @requests.delete(request)
|
|
161
203
|
connection = reset_hostname(host)
|
|
162
204
|
emit_resolve_error(connection, connection.peer.host, result)
|
|
205
|
+
close_or_resolve
|
|
163
206
|
end
|
|
164
207
|
end
|
|
165
208
|
|
|
@@ -169,6 +212,7 @@ module HTTPX
|
|
|
169
212
|
host = @requests.delete(request)
|
|
170
213
|
connection = reset_hostname(host)
|
|
171
214
|
emit_resolve_error(connection)
|
|
215
|
+
close_or_resolve
|
|
172
216
|
return
|
|
173
217
|
|
|
174
218
|
else
|
|
@@ -207,24 +251,25 @@ module HTTPX
|
|
|
207
251
|
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) }) }
|
|
208
252
|
end
|
|
209
253
|
end
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
resolve
|
|
254
|
+
close_or_resolve(true)
|
|
213
255
|
end
|
|
214
256
|
|
|
215
257
|
def build_request(hostname)
|
|
216
258
|
uri = @uri.dup
|
|
217
259
|
rklass = @options.request_class
|
|
218
260
|
payload = Resolver.encode_dns_query(hostname, type: @record_type)
|
|
261
|
+
timeouts = @timeouts[hostname]
|
|
262
|
+
request_timeout = timeouts.shift
|
|
263
|
+
options = @options.merge(timeout: { request_timeout: request_timeout })
|
|
219
264
|
|
|
220
265
|
if @resolver_options[:use_get]
|
|
221
266
|
params = URI.decode_www_form(uri.query.to_s)
|
|
222
267
|
params << ["type", FAMILY_TYPES[@record_type]]
|
|
223
268
|
params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
|
|
224
269
|
uri.query = URI.encode_www_form(params)
|
|
225
|
-
request = rklass.new("GET", uri,
|
|
270
|
+
request = rklass.new("GET", uri, options)
|
|
226
271
|
else
|
|
227
|
-
request = rklass.new("POST", uri,
|
|
272
|
+
request = rklass.new("POST", uri, options, body: [payload])
|
|
228
273
|
request.headers["content-type"] = "application/dns-message"
|
|
229
274
|
end
|
|
230
275
|
request.headers["accept"] = "application/dns-message"
|
|
@@ -242,6 +287,7 @@ module HTTPX
|
|
|
242
287
|
end
|
|
243
288
|
|
|
244
289
|
def reset_hostname(hostname, reset_candidates: true)
|
|
290
|
+
@timeouts.delete(hostname)
|
|
245
291
|
connection = @queries.delete(hostname)
|
|
246
292
|
|
|
247
293
|
return connection unless connection && reset_candidates
|
|
@@ -249,8 +295,28 @@ module HTTPX
|
|
|
249
295
|
# eliminate other candidates
|
|
250
296
|
candidates = @queries.select { |_, conn| connection == conn }.keys
|
|
251
297
|
@queries.delete_if { |h, _| candidates.include?(h) }
|
|
298
|
+
# reset timeouts
|
|
299
|
+
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
|
252
300
|
|
|
253
301
|
connection
|
|
254
302
|
end
|
|
303
|
+
|
|
304
|
+
def close_or_resolve(should_deactivate = false)
|
|
305
|
+
# drop already closed connections
|
|
306
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
|
307
|
+
|
|
308
|
+
if (@connections - @queries.values).empty?
|
|
309
|
+
# the same resolver connection may be serving different https resolvers (AAAA and A).
|
|
310
|
+
return if inflight?
|
|
311
|
+
|
|
312
|
+
if should_deactivate
|
|
313
|
+
deactivate
|
|
314
|
+
else
|
|
315
|
+
disconnect
|
|
316
|
+
end
|
|
317
|
+
else
|
|
318
|
+
resolve
|
|
319
|
+
end
|
|
320
|
+
end
|
|
255
321
|
end
|
|
256
322
|
end
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -5,9 +5,6 @@ require "resolv"
|
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
7
|
class Resolver::Multi
|
|
8
|
-
include Callbacks
|
|
9
|
-
using ArrayExtensions::FilterMap
|
|
10
|
-
|
|
11
8
|
attr_reader :resolvers, :options
|
|
12
9
|
|
|
13
10
|
def initialize(resolver_type, options)
|
|
@@ -26,44 +23,28 @@ module HTTPX
|
|
|
26
23
|
@errors = Hash.new { |hs, k| hs[k] = [] }
|
|
27
24
|
end
|
|
28
25
|
|
|
26
|
+
def state
|
|
27
|
+
@resolvers.map(&:state).uniq.join(",")
|
|
28
|
+
end
|
|
29
|
+
|
|
29
30
|
def current_selector=(s)
|
|
30
31
|
@current_selector = s
|
|
31
|
-
@resolvers.each { |r| r.
|
|
32
|
+
@resolvers.each { |r| r.current_selector = s }
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def current_session=(s)
|
|
35
36
|
@current_session = s
|
|
36
|
-
@resolvers.each { |r| r.
|
|
37
|
+
@resolvers.each { |r| r.current_session = s }
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def log(*args, **kwargs, &blk)
|
|
40
|
-
@resolvers.each { |r| r.
|
|
41
|
+
@resolvers.each { |r| r.log(*args, **kwargs, &blk) }
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def closed?
|
|
44
45
|
@resolvers.all?(&:closed?)
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
def empty?
|
|
48
|
-
@resolvers.all?(&:empty?)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def inflight?
|
|
52
|
-
@resolvers.any(&:inflight?)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def timeout
|
|
56
|
-
@resolvers.filter_map(&:timeout).min
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def close
|
|
60
|
-
@resolvers.each(&:close)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def connections
|
|
64
|
-
@resolvers.filter_map { |r| r.resolver_connection if r.respond_to?(:resolver_connection) }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
48
|
def early_resolve(connection)
|
|
68
49
|
hostname = connection.peer.host
|
|
69
50
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
|
@@ -92,9 +73,13 @@ module HTTPX
|
|
|
92
73
|
|
|
93
74
|
def lazy_resolve(connection)
|
|
94
75
|
@resolvers.each do |resolver|
|
|
95
|
-
|
|
76
|
+
conn_to_resolve = @current_session.try_clone_connection(connection, @current_selector, resolver.family)
|
|
77
|
+
resolver << conn_to_resolve
|
|
78
|
+
|
|
96
79
|
next if resolver.empty?
|
|
97
80
|
|
|
81
|
+
# both the resolver and the connection it's resolving must be pineed to the session
|
|
82
|
+
@current_session.pin(conn_to_resolve, @current_selector)
|
|
98
83
|
@current_session.select_resolver(resolver, @current_selector)
|
|
99
84
|
end
|
|
100
85
|
end
|