httpx 0.19.4 → 0.19.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/0_19_4.md +3 -2
- data/doc/release_notes/0_19_5.md +13 -0
- data/lib/httpx/headers.rb +2 -0
- data/lib/httpx/options.rb +3 -2
- data/lib/httpx/plugins/proxy/ssh.rb +0 -6
- data/lib/httpx/plugins/proxy.rb +0 -6
- data/lib/httpx/resolver/https.rb +33 -13
- data/lib/httpx/resolver/native.rb +52 -29
- data/lib/httpx/resolver/system.rb +2 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/resolver/https.rbs +2 -2
- data/sig/resolver/native.rbs +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0dbbe59cf004c889830daa1a45b0111eb4e04e7447fc0e94bca9ae7041d99770
|
4
|
+
data.tar.gz: 95acfba594d42b96a41e0d18d682f5873cbe57e1e394d62d76ce7be1463741ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 401ee8e3e4c1eb92a454a9debb73d3f1c7d1f119ba757e5a2534b4d2b7a39c2608b7ef91f95fc74ec50dfa7db1faa25aadf4b57d2e8c9f58f5c65e01f98dc234
|
7
|
+
data.tar.gz: 937d4b05a7ee6b3da3cdce1e98e3c389dc3eed82cc29a278dfb3d7c0d7907e795421caaec00f2a95112d99deb13fcfe1d81a70c291d0f00dd862021cbe036583
|
data/doc/release_notes/0_19_4.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# 0.19.
|
1
|
+
# 0.19.4
|
2
2
|
|
3
3
|
## Improvements
|
4
4
|
|
@@ -10,4 +10,5 @@ The (optional) FFI-based TLS module for jruby was deleted. Besides it being cumb
|
|
10
10
|
|
11
11
|
* `webmock` integration was fixed to take the mocked URI query string into account.
|
12
12
|
* fix internal codepath where mergeable-but-not-coalescable connections were still triggering the coalesce branch.
|
13
|
-
* fixed after-use mutation of connection addresses array which was making it empty after initial usage.
|
13
|
+
* fixed after-use mutation of connection addresses array which was making it empty after initial usage.
|
14
|
+
* fixed a "busy loop" caused by long-running native resolver not signaling it had "nothing to do".
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# 0.19.5
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### DNS: resolv.conf search/ndots options support (native/https resolvers)
|
6
|
+
|
7
|
+
Both the native (default) as well as the HTTPS (DoH) resolvers now support the "search" and "ndots" options, which adds domain "suffixes" under certain conditions to be used in name resolutions (this is a quite common feature found in kubernetes pods).
|
8
|
+
|
9
|
+
(While this means a new feature is being shipped in a patch release, one can argue that this feature "fixes" DNS in `httpx`.)
|
10
|
+
|
11
|
+
## Bugfixes
|
12
|
+
|
13
|
+
* skipping headers comparison in HTTPX::Options#==; this had the unintended consequence of breaking connection reuse when crafting requests in a certain way, thereby making every request to the same origin issue their own connection, resulting, in multi-request scenarios (and with the `:persistent` plugin), in the process exhausting the max amount of allowed file descriptors.
|
data/lib/httpx/headers.rb
CHANGED
data/lib/httpx/options.rb
CHANGED
@@ -209,8 +209,9 @@ module HTTPX
|
|
209
209
|
ivars.all? do |ivar|
|
210
210
|
case ivar
|
211
211
|
when :@headers
|
212
|
-
|
213
|
-
headers.
|
212
|
+
# currently, this is used to pick up an available matching connection.
|
213
|
+
# the headers do not play a role, as they are relevant only for the request.
|
214
|
+
true
|
214
215
|
when *REQUEST_IVARS
|
215
216
|
true
|
216
217
|
else
|
@@ -78,12 +78,6 @@ module HTTPX
|
|
78
78
|
end
|
79
79
|
|
80
80
|
module ConnectionMethods
|
81
|
-
def match?(uri, options)
|
82
|
-
return super unless @options.proxy
|
83
|
-
|
84
|
-
super && @options.proxy == options.proxy
|
85
|
-
end
|
86
|
-
|
87
81
|
# should not coalesce connections here, as the IP is the IP of the proxy
|
88
82
|
def coalescable?(*)
|
89
83
|
return super unless @options.proxy
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -174,12 +174,6 @@ module HTTPX
|
|
174
174
|
@origin.port = proxy_uri.port
|
175
175
|
end
|
176
176
|
|
177
|
-
def match?(uri, options)
|
178
|
-
return super unless @options.proxy
|
179
|
-
|
180
|
-
super && @options.proxy == options.proxy
|
181
|
-
end
|
182
|
-
|
183
177
|
def coalescable?(connection)
|
184
178
|
return super unless @options.proxy
|
185
179
|
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -11,6 +11,15 @@ module HTTPX
|
|
11
11
|
using URIExtensions
|
12
12
|
using StringExtensions
|
13
13
|
|
14
|
+
module DNSExtensions
|
15
|
+
refine Resolv::DNS do
|
16
|
+
def generate_candidates(name)
|
17
|
+
@config.generate_candidates(name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
using DNSExtensions
|
22
|
+
|
14
23
|
NAMESERVER = "https://1.1.1.1/dns-query"
|
15
24
|
|
16
25
|
DEFAULTS = {
|
@@ -76,30 +85,37 @@ module HTTPX
|
|
76
85
|
if hostname.nil?
|
77
86
|
hostname = connection.origin.host
|
78
87
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
88
|
+
|
89
|
+
hostname = @resolver.generate_candidates(hostname).each do |name|
|
90
|
+
@queries[name.to_s] = connection
|
91
|
+
end.first.to_s
|
92
|
+
else
|
93
|
+
@queries[hostname] = connection
|
79
94
|
end
|
80
95
|
log { "resolver: query #{FAMILY_TYPES[RECORD_TYPES[@family]]} for #{hostname}" }
|
96
|
+
|
81
97
|
begin
|
82
98
|
request = build_request(hostname)
|
83
99
|
request.on(:response, &method(:on_response).curry(2)[request])
|
84
100
|
request.on(:promise, &method(:on_promise))
|
85
|
-
@requests[request] =
|
101
|
+
@requests[request] = hostname
|
86
102
|
resolver_connection.send(request)
|
87
|
-
@queries[hostname] = connection
|
88
103
|
@connections << connection
|
89
104
|
rescue ResolveError, Resolv::DNS::EncodeError, JSON::JSONError => e
|
90
|
-
|
105
|
+
@queries.delete(hostname)
|
106
|
+
emit_resolve_error(connection, connection.origin.host, e)
|
91
107
|
end
|
92
108
|
end
|
93
109
|
|
94
110
|
def on_response(request, response)
|
95
111
|
response.raise_for_status
|
96
112
|
rescue StandardError => e
|
97
|
-
|
98
|
-
|
99
|
-
emit_resolve_error(connection,
|
113
|
+
hostname = @requests.delete(request)
|
114
|
+
connection = @queries.delete(hostname)
|
115
|
+
emit_resolve_error(connection, connection.origin.host, e)
|
100
116
|
else
|
101
117
|
# @type var response: HTTPX::Response
|
102
|
-
parse(response)
|
118
|
+
parse(request, response)
|
103
119
|
ensure
|
104
120
|
@requests.delete(request)
|
105
121
|
end
|
@@ -109,20 +125,21 @@ module HTTPX
|
|
109
125
|
stream.refuse
|
110
126
|
end
|
111
127
|
|
112
|
-
def parse(response)
|
128
|
+
def parse(request, response)
|
113
129
|
begin
|
114
130
|
answers = decode_response_body(response)
|
115
131
|
rescue Resolv::DNS::DecodeError, JSON::JSONError => e
|
116
132
|
host, connection = @queries.first
|
117
133
|
@queries.delete(host)
|
118
|
-
emit_resolve_error(connection, host, e)
|
134
|
+
emit_resolve_error(connection, connection.origin.host, e)
|
119
135
|
return
|
120
136
|
end
|
121
137
|
if answers.nil? || answers.empty?
|
122
|
-
host
|
123
|
-
@queries.delete(host)
|
124
|
-
emit_resolve_error(connection
|
138
|
+
host = @requests.delete(request)
|
139
|
+
connection = @queries.delete(host)
|
140
|
+
emit_resolve_error(connection)
|
125
141
|
return
|
142
|
+
|
126
143
|
else
|
127
144
|
answers = answers.group_by { |answer| answer["name"] }
|
128
145
|
answers.each do |hostname, addresses|
|
@@ -130,7 +147,6 @@ module HTTPX
|
|
130
147
|
if address.key?("alias")
|
131
148
|
alias_address = answers[address["alias"]]
|
132
149
|
if alias_address.nil?
|
133
|
-
connection = @queries[hostname]
|
134
150
|
@queries.delete(address["name"])
|
135
151
|
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
|
136
152
|
@connections.delete(connection)
|
@@ -152,6 +168,10 @@ module HTTPX
|
|
152
168
|
next unless connection # probably a retried query for which there's an answer
|
153
169
|
|
154
170
|
@connections.delete(connection)
|
171
|
+
|
172
|
+
# eliminate other candidates
|
173
|
+
@queries.delete_if { |_, conn| connection == conn }
|
174
|
+
|
155
175
|
Resolver.cached_lookup_set(hostname, @family, addresses) if @resolver_options[:cache]
|
156
176
|
emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
|
157
177
|
end
|
@@ -46,6 +46,8 @@ module HTTPX
|
|
46
46
|
@ns_index = 0
|
47
47
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
48
48
|
@nameserver = @resolver_options[:nameserver]
|
49
|
+
@ndots = @resolver_options[:ndots]
|
50
|
+
@search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
|
49
51
|
@_timeouts = Array(@resolver_options[:timeouts])
|
50
52
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
51
53
|
@connections = []
|
@@ -136,33 +138,32 @@ module HTTPX
|
|
136
138
|
return if @queries.empty? || !@start_timeout
|
137
139
|
|
138
140
|
loop_time = Utils.elapsed_time(@start_timeout)
|
139
|
-
connections = []
|
140
|
-
queries = {}
|
141
|
-
while (query = @queries.shift)
|
142
|
-
h, connection = query
|
143
|
-
host = connection.origin.host
|
144
|
-
timeout = (@timeouts[host][0] -= loop_time)
|
145
|
-
unless timeout.negative?
|
146
|
-
queries[h] = connection
|
147
|
-
next
|
148
|
-
end
|
149
141
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
142
|
+
query = @queries.first
|
143
|
+
|
144
|
+
return unless query
|
145
|
+
|
146
|
+
h, connection = query
|
147
|
+
host = connection.origin.host
|
148
|
+
timeout = (@timeouts[host][0] -= loop_time)
|
149
|
+
|
150
|
+
return unless timeout.negative?
|
151
|
+
|
152
|
+
@timeouts[host].shift
|
153
|
+
if @timeouts[host].empty?
|
154
|
+
@timeouts.delete(host)
|
155
|
+
@queries.delete(h)
|
156
|
+
|
157
|
+
return unless @queries.empty?
|
158
|
+
|
159
|
+
@connections.delete(connection)
|
160
|
+
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
161
|
+
# resolve timeout, including from the previous retries.
|
162
|
+
raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.origin.host}")
|
163
|
+
else
|
164
|
+
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
165
|
+
resolve(connection)
|
163
166
|
end
|
164
|
-
@queries = queries
|
165
|
-
connections.each { |ch| resolve(ch) }
|
166
167
|
end
|
167
168
|
|
168
169
|
def dread(wsize = @resolver_options[:packet_size])
|
@@ -194,7 +195,7 @@ module HTTPX
|
|
194
195
|
@queries.delete(hostname)
|
195
196
|
@timeouts.delete(hostname)
|
196
197
|
@connections.delete(connection)
|
197
|
-
ex = NativeResolveError.new(connection,
|
198
|
+
ex = NativeResolveError.new(connection, connection.origin.host, e.message)
|
198
199
|
ex.set_backtrace(e.backtrace)
|
199
200
|
raise ex
|
200
201
|
end
|
@@ -203,9 +204,11 @@ module HTTPX
|
|
203
204
|
hostname, connection = @queries.first
|
204
205
|
@queries.delete(hostname)
|
205
206
|
@timeouts.delete(hostname)
|
206
|
-
@connections.delete(connection)
|
207
207
|
|
208
|
-
|
208
|
+
unless @queries.value?(connection)
|
209
|
+
@connections.delete(connection)
|
210
|
+
raise NativeResolveError.new(connection, connection.origin.host)
|
211
|
+
end
|
209
212
|
else
|
210
213
|
address = addresses.first
|
211
214
|
name = address["name"]
|
@@ -224,6 +227,9 @@ module HTTPX
|
|
224
227
|
connection = @queries.delete(name)
|
225
228
|
end
|
226
229
|
|
230
|
+
# eliminate other candidates
|
231
|
+
@queries.delete_if { |_, conn| connection == conn }
|
232
|
+
|
227
233
|
if address.key?("alias") # CNAME
|
228
234
|
# clean up intermediate queries
|
229
235
|
@timeouts.delete(name) unless connection.origin.host == name
|
@@ -256,8 +262,13 @@ module HTTPX
|
|
256
262
|
if hostname.nil?
|
257
263
|
hostname = connection.origin.host
|
258
264
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
265
|
+
|
266
|
+
hostname = generate_candidates(hostname).each do |name|
|
267
|
+
@queries[name] = connection
|
268
|
+
end.first
|
269
|
+
else
|
270
|
+
@queries[hostname] = connection
|
259
271
|
end
|
260
|
-
@queries[hostname] = connection
|
261
272
|
log { "resolver: query #{@record_type.name.split("::").last} for #{hostname}" }
|
262
273
|
begin
|
263
274
|
@write_buffer << Resolver.encode_dns_query(hostname, type: @record_type)
|
@@ -266,6 +277,18 @@ module HTTPX
|
|
266
277
|
end
|
267
278
|
end
|
268
279
|
|
280
|
+
def generate_candidates(name)
|
281
|
+
return [name] if name.end_with?(".")
|
282
|
+
|
283
|
+
candidates = []
|
284
|
+
name_parts = name.scan(/[^.]+/)
|
285
|
+
candidates = [name] if @ndots <= name_parts.size - 1
|
286
|
+
candidates.concat(@search.map { |domain| [*name_parts, *domain].join(".") })
|
287
|
+
candidates << name unless candidates.include?(name)
|
288
|
+
|
289
|
+
candidates
|
290
|
+
end
|
291
|
+
|
269
292
|
def build_socket
|
270
293
|
return if @io
|
271
294
|
|
data/lib/httpx/version.rb
CHANGED
data/sig/resolver/https.rbs
CHANGED
@@ -8,7 +8,7 @@ module HTTPX
|
|
8
8
|
|
9
9
|
@family: ip_family
|
10
10
|
@options: Options
|
11
|
-
@requests: Hash[Request,
|
11
|
+
@requests: Hash[Request, String]
|
12
12
|
@connections: Array[Connection]
|
13
13
|
@uri: URI::Generic
|
14
14
|
@uri_addresses: Array[String]?
|
@@ -29,7 +29,7 @@ module HTTPX
|
|
29
29
|
|
30
30
|
def on_response: (Request, response) -> void
|
31
31
|
|
32
|
-
def parse: (Response response) -> void
|
32
|
+
def parse: (Request request, Response response) -> void
|
33
33
|
|
34
34
|
def build_request: (String hostname) -> Request
|
35
35
|
|
data/sig/resolver/native.rbs
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httpx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.19.
|
4
|
+
version: 0.19.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2-next
|
@@ -75,6 +75,7 @@ extra_rdoc_files:
|
|
75
75
|
- doc/release_notes/0_19_2.md
|
76
76
|
- doc/release_notes/0_19_3.md
|
77
77
|
- doc/release_notes/0_19_4.md
|
78
|
+
- doc/release_notes/0_19_5.md
|
78
79
|
- doc/release_notes/0_1_0.md
|
79
80
|
- doc/release_notes/0_2_0.md
|
80
81
|
- doc/release_notes/0_2_1.md
|
@@ -143,6 +144,7 @@ files:
|
|
143
144
|
- doc/release_notes/0_19_2.md
|
144
145
|
- doc/release_notes/0_19_3.md
|
145
146
|
- doc/release_notes/0_19_4.md
|
147
|
+
- doc/release_notes/0_19_5.md
|
146
148
|
- doc/release_notes/0_1_0.md
|
147
149
|
- doc/release_notes/0_2_0.md
|
148
150
|
- doc/release_notes/0_2_1.md
|