httpx 0.19.2 → 0.19.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9e79c1b196931855e7af542bd611050a95648d2888d539cb20de5026c4a3ccf
4
- data.tar.gz: 4cc919ede2c58d6bcf86433ddf4ceba9fdca5416186d5984fa2f29a92707668d
3
+ metadata.gz: 0dbbe59cf004c889830daa1a45b0111eb4e04e7447fc0e94bca9ae7041d99770
4
+ data.tar.gz: 95acfba594d42b96a41e0d18d682f5873cbe57e1e394d62d76ce7be1463741ad
5
5
  SHA512:
6
- metadata.gz: 5a35e9f740d6df65190a8e28cc2d2d8adfe9c208842290ceb0c8f6c9dd273e19066c7cc851d917f6daac663c205401e7df26916a0f41c01653e53488d404f669
7
- data.tar.gz: 67203d583847abee46ac940ff82c11624298907e191ff0c6c5408b1cddf0c909ac8820de24f11ef1cc9ac7030e5d11e807668e9f991967fc4bb4654f92ddd3c8
6
+ metadata.gz: 401ee8e3e4c1eb92a454a9debb73d3f1c7d1f119ba757e5a2534b4d2b7a39c2608b7ef91f95fc74ec50dfa7db1faa25aadf4b57d2e8c9f58f5c65e01f98dc234
7
+ data.tar.gz: 937d4b05a7ee6b3da3cdce1e98e3c389dc3eed82cc29a278dfb3d7c0d7907e795421caaec00f2a95112d99deb13fcfe1d81a70c291d0f00dd862021cbe036583
@@ -0,0 +1,6 @@
1
+ # 0.19.3
2
+
3
+ ## Bugfixes
4
+
5
+ * `retries` plugin: allow passing floats to `:retry_after` option.
6
+ * dns: fixing cache lookups filtering by IP family which was causing socket connect handshake to start with no IP.
@@ -0,0 +1,14 @@
1
+ # 0.19.4
2
+
3
+ ## Improvements
4
+
5
+ ### Jruby: HTTP/2 with jruby-openssl (>= 0.12.2)
6
+
7
+ The (optional) FFI-based TLS module for jruby was deleted. Besides it being cumbersome and hard to maintain, `jruby`'s own `openssl` released support for ALPN negotiation (in v0.12.2), which solves the problem the deleted module was supposed to address.
8
+
9
+ ## Bugfixes
10
+
11
+ * `webmock` integration was fixed to take the mocked URI query string into account.
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.
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.
@@ -19,6 +19,7 @@ module WebMock
19
19
  class << self
20
20
  def build_webmock_request_signature(request)
21
21
  uri = WebMock::Util::URI.heuristic_parse(request.uri)
22
+ uri.query = request.query
22
23
  uri.path = uri.normalized_path.gsub("[^:]//", "/")
23
24
 
24
25
  WebMock::RequestSignature.new(
data/lib/httpx/headers.rb CHANGED
@@ -42,6 +42,8 @@ module HTTPX
42
42
  def same_headers?(headers)
43
43
  @headers.empty? || begin
44
44
  headers.each do |k, v|
45
+ next unless key?(k)
46
+
45
47
  return false unless v == self[k]
46
48
  end
47
49
  true
data/lib/httpx/io.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require "httpx/io/udp"
4
5
  require "httpx/io/tcp"
5
6
  require "httpx/io/unix"
6
- require "httpx/io/udp"
7
+ require "httpx/io/ssl"
7
8
 
8
9
  module HTTPX
9
10
  module IO
@@ -11,20 +12,6 @@ module HTTPX
11
12
  register "udp", UDP
12
13
  register "unix", HTTPX::UNIX
13
14
  register "tcp", TCP
14
-
15
- if RUBY_ENGINE == "jruby"
16
- begin
17
- require "httpx/io/tls"
18
- register "ssl", TLS
19
- rescue LoadError
20
- # :nocov:
21
- require "httpx/io/ssl"
22
- register "ssl", SSL
23
- # :nocov:
24
- end
25
- else
26
- require "httpx/io/ssl"
27
- register "ssl", SSL
28
- end
15
+ register "ssl", SSL
29
16
  end
30
17
  end
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
- headers = instance_variable_get(ivar)
213
- headers.same_headers?(other.instance_variable_get(ivar))
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
@@ -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
 
@@ -41,7 +41,7 @@ module HTTPX
41
41
  def option_retry_after(value)
42
42
  # return early if callable
43
43
  unless value.respond_to?(:call)
44
- value = Integer(value)
44
+ value = Float(value)
45
45
  raise TypeError, ":retry_after must be positive" unless value.positive?
46
46
  end
47
47
 
data/lib/httpx/pool.rb CHANGED
@@ -133,7 +133,7 @@ module HTTPX
133
133
 
134
134
  if found_connection.open?
135
135
  coalesce_connections(found_connection, connection)
136
- throw(:coalesced, found_connection)
136
+ throw(:coalesced, found_connection) unless @connections.include?(connection)
137
137
  else
138
138
  found_connection.once(:open) do
139
139
  coalesce_connections(found_connection, connection)
@@ -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] = connection
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
- emit_resolve_error(connection, hostname, e)
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
- connection = @requests[request]
98
- hostname = @queries.key(connection)
99
- emit_resolve_error(connection, hostname, e)
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, connection = @queries.first
123
- @queries.delete(host)
124
- emit_resolve_error(connection, host)
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 = []
@@ -119,7 +121,11 @@ module HTTPX
119
121
  private
120
122
 
121
123
  def calculate_interests
122
- !@write_buffer.empty? || @queries.empty? ? :w : :r
124
+ return :w unless @write_buffer.empty?
125
+
126
+ return :r unless @queries.empty?
127
+
128
+ nil
123
129
  end
124
130
 
125
131
  def consume
@@ -132,33 +138,32 @@ module HTTPX
132
138
  return if @queries.empty? || !@start_timeout
133
139
 
134
140
  loop_time = Utils.elapsed_time(@start_timeout)
135
- connections = []
136
- queries = {}
137
- while (query = @queries.shift)
138
- h, connection = query
139
- host = connection.origin.host
140
- timeout = (@timeouts[host][0] -= loop_time)
141
- unless timeout.negative?
142
- queries[h] = connection
143
- next
144
- end
145
141
 
146
- @timeouts[host].shift
147
- if @timeouts[host].empty?
148
- @timeouts.delete(host)
149
- @connections.delete(connection)
150
- # This loop_time passed to the exception is bogus. Ideally we would pass the total
151
- # resolve timeout, including from the previous retries.
152
- raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{host}")
153
- # raise NativeResolveError.new(connection, host)
154
- else
155
- log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
156
- connections << connection
157
- queries[h] = connection
158
- end
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)
159
166
  end
160
- @queries = queries
161
- connections.each { |ch| resolve(ch) }
162
167
  end
163
168
 
164
169
  def dread(wsize = @resolver_options[:packet_size])
@@ -190,7 +195,7 @@ module HTTPX
190
195
  @queries.delete(hostname)
191
196
  @timeouts.delete(hostname)
192
197
  @connections.delete(connection)
193
- ex = NativeResolveError.new(connection, hostname, e.message)
198
+ ex = NativeResolveError.new(connection, connection.origin.host, e.message)
194
199
  ex.set_backtrace(e.backtrace)
195
200
  raise ex
196
201
  end
@@ -199,9 +204,11 @@ module HTTPX
199
204
  hostname, connection = @queries.first
200
205
  @queries.delete(hostname)
201
206
  @timeouts.delete(hostname)
202
- @connections.delete(connection)
203
207
 
204
- raise NativeResolveError.new(connection, hostname)
208
+ unless @queries.value?(connection)
209
+ @connections.delete(connection)
210
+ raise NativeResolveError.new(connection, connection.origin.host)
211
+ end
205
212
  else
206
213
  address = addresses.first
207
214
  name = address["name"]
@@ -220,6 +227,9 @@ module HTTPX
220
227
  connection = @queries.delete(name)
221
228
  end
222
229
 
230
+ # eliminate other candidates
231
+ @queries.delete_if { |_, conn| connection == conn }
232
+
223
233
  if address.key?("alias") # CNAME
224
234
  # clean up intermediate queries
225
235
  @timeouts.delete(name) unless connection.origin.host == name
@@ -252,8 +262,13 @@ module HTTPX
252
262
  if hostname.nil?
253
263
  hostname = connection.origin.host
254
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
255
271
  end
256
- @queries[hostname] = connection
257
272
  log { "resolver: query #{@record_type.name.split("::").last} for #{hostname}" }
258
273
  begin
259
274
  @write_buffer << Resolver.encode_dns_query(hostname, type: @record_type)
@@ -262,6 +277,18 @@ module HTTPX
262
277
  end
263
278
  end
264
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
+
265
292
  def build_socket
266
293
  return if @io
267
294
 
@@ -72,7 +72,9 @@ module HTTPX
72
72
 
73
73
  return unless addresses
74
74
 
75
- addresses.select! { |addr| addr.family == @family }
75
+ addresses = addresses.select { |addr| addr.family == @family }
76
+
77
+ return if addresses.empty?
76
78
 
77
79
  emit_addresses(connection, @family, addresses)
78
80
  end
@@ -82,6 +82,8 @@ module HTTPX
82
82
 
83
83
  _, connection = @queries.first
84
84
 
85
+ return unless connection
86
+
85
87
  @timeouts[connection.origin.host].first
86
88
  end
87
89
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.19.2"
4
+ VERSION = "0.19.5"
5
5
  end
@@ -8,7 +8,7 @@ module HTTPX
8
8
 
9
9
  @family: ip_family
10
10
  @options: Options
11
- @requests: Hash[Request, Connection]
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
 
@@ -21,7 +21,7 @@ module HTTPX
21
21
 
22
22
  def call: () -> void
23
23
 
24
- def interests: () -> io_interests
24
+ def interests: () -> (:r | :w | nil)
25
25
 
26
26
  def <<: (Connection) -> void
27
27
 
@@ -31,7 +31,7 @@ module HTTPX
31
31
 
32
32
  def initialize: (ip_family family, options options) -> void
33
33
 
34
- def calculate_interests: () -> (:r | :w)
34
+ def calculate_interests: () -> (:r | :w | nil)
35
35
 
36
36
  def consume: () -> void
37
37
 
@@ -46,6 +46,8 @@ module HTTPX
46
46
 
47
47
  def resolve: (?Connection connection, ?String hostname) -> void
48
48
 
49
+ def generate_candidates: (String) -> Array[String]
50
+
49
51
  def build_socket: () -> void
50
52
 
51
53
  def transition: (Symbol nextstate) -> void
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.2
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-02-13 00:00:00.000000000 Z
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
@@ -73,6 +73,9 @@ extra_rdoc_files:
73
73
  - doc/release_notes/0_19_0.md
74
74
  - doc/release_notes/0_19_1.md
75
75
  - doc/release_notes/0_19_2.md
76
+ - doc/release_notes/0_19_3.md
77
+ - doc/release_notes/0_19_4.md
78
+ - doc/release_notes/0_19_5.md
76
79
  - doc/release_notes/0_1_0.md
77
80
  - doc/release_notes/0_2_0.md
78
81
  - doc/release_notes/0_2_1.md
@@ -139,6 +142,9 @@ files:
139
142
  - doc/release_notes/0_19_0.md
140
143
  - doc/release_notes/0_19_1.md
141
144
  - doc/release_notes/0_19_2.md
145
+ - doc/release_notes/0_19_3.md
146
+ - doc/release_notes/0_19_4.md
147
+ - doc/release_notes/0_19_5.md
142
148
  - doc/release_notes/0_1_0.md
143
149
  - doc/release_notes/0_2_0.md
144
150
  - doc/release_notes/0_2_1.md
@@ -179,10 +185,6 @@ files:
179
185
  - lib/httpx/io.rb
180
186
  - lib/httpx/io/ssl.rb
181
187
  - lib/httpx/io/tcp.rb
182
- - lib/httpx/io/tls.rb
183
- - lib/httpx/io/tls/box.rb
184
- - lib/httpx/io/tls/context.rb
185
- - lib/httpx/io/tls/ffi.rb
186
188
  - lib/httpx/io/udp.rb
187
189
  - lib/httpx/io/unix.rb
188
190
  - lib/httpx/loggable.rb