httpx 0.19.1 → 0.19.4

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: 56e51d7951de9e5964eb1106d8829504e1d0fbac2ca7906b64f6139467159403
4
- data.tar.gz: 6b7726c50101f8356cf04628ee9ff32cbd6babd3bf98f426d768ae52d835f4a0
3
+ metadata.gz: c1c8ae2cc46f5a4033ea5c776bef1a7703cfc27834818cded1487101b8a6bbbe
4
+ data.tar.gz: ea445112ce42d2d81c4465f646687d605da855e3248838d26075388463250d9b
5
5
  SHA512:
6
- metadata.gz: d29f216b4176e5085d7495d3ac015eb4b9f7e76f4dd064ae1f822890cc44d1718ec14fe9a3935515af6c7882762ebd875a0e15983d5707a2c9f68d069bfd084c
7
- data.tar.gz: 30537730f11c0bfffd43882abd430c8a006a2a863b7ef247286f085440781d1b44e048d031df2c3af95fffc9d5ce04fcaad6bd0d05d039fc77023a0dc95a7508
6
+ metadata.gz: effaefc6365aa24fe86a62bca9bdbfed701dc9c19354c6bf7f046ea2bf84b94f908e1924381f2e27cfe748c7ad4e0b8d97dfa0a6d695775cec8d1b8a92760fe7
7
+ data.tar.gz: 55f846f8412acb99008cd18834ce50fe73ae072a0446d5d4fdf76fe2c48be4b2a863721b18191ca406a60b6883a8e35cba2f856c07ce9fa8ba808476b5b66887
@@ -0,0 +1,7 @@
1
+ # 0.19.2
2
+
3
+ ## Bugfixes
4
+
5
+ * skip resolution delay path for early resolve cases
6
+
7
+ when the early resolve path (using IP, /etc/hosts IP, IP from cache) is followed, emit_addresses is called, and in a particular case (dual-stack network but using an IPv4 address), the happy eyeballs resolution delay path was activated when it shouldn't (it's meant to be used only for DNS network requests), and resulted in @pool being called before it was ever set. This simple check ensures that it doesn't happen before it must.
@@ -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,13 @@
1
+ # 0.19.3
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.
@@ -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/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
@@ -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)
@@ -119,7 +119,11 @@ module HTTPX
119
119
  private
120
120
 
121
121
  def calculate_interests
122
- !@write_buffer.empty? || @queries.empty? ? :w : :r
122
+ return :w unless @write_buffer.empty?
123
+
124
+ return :r unless @queries.empty?
125
+
126
+ nil
123
127
  end
124
128
 
125
129
  def consume
@@ -49,7 +49,8 @@ module HTTPX
49
49
  address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
50
50
  end
51
51
  log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
52
- if !connection.io &&
52
+ if @pool && # if triggered by early resolve, pool may not be here yet
53
+ !connection.io &&
53
54
  connection.options.ip_families.size > 1 &&
54
55
  family == Socket::AF_INET &&
55
56
  addresses.first.to_s != connection.origin.host.to_s
@@ -71,7 +72,9 @@ module HTTPX
71
72
 
72
73
  return unless addresses
73
74
 
74
- addresses.select! { |addr| addr.family == @family }
75
+ addresses = addresses.select { |addr| addr.family == @family }
76
+
77
+ return if addresses.empty?
75
78
 
76
79
  emit_addresses(connection, @family, addresses)
77
80
  end
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.1"
4
+ VERSION = "0.19.4"
5
5
  end
@@ -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
 
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.1
4
+ version: 0.19.4
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-11 00:00:00.000000000 Z
11
+ date: 2022-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -72,6 +72,9 @@ extra_rdoc_files:
72
72
  - doc/release_notes/0_18_7.md
73
73
  - doc/release_notes/0_19_0.md
74
74
  - doc/release_notes/0_19_1.md
75
+ - doc/release_notes/0_19_2.md
76
+ - doc/release_notes/0_19_3.md
77
+ - doc/release_notes/0_19_4.md
75
78
  - doc/release_notes/0_1_0.md
76
79
  - doc/release_notes/0_2_0.md
77
80
  - doc/release_notes/0_2_1.md
@@ -137,6 +140,9 @@ files:
137
140
  - doc/release_notes/0_18_7.md
138
141
  - doc/release_notes/0_19_0.md
139
142
  - doc/release_notes/0_19_1.md
143
+ - doc/release_notes/0_19_2.md
144
+ - doc/release_notes/0_19_3.md
145
+ - doc/release_notes/0_19_4.md
140
146
  - doc/release_notes/0_1_0.md
141
147
  - doc/release_notes/0_2_0.md
142
148
  - doc/release_notes/0_2_1.md
@@ -177,10 +183,6 @@ files:
177
183
  - lib/httpx/io.rb
178
184
  - lib/httpx/io/ssl.rb
179
185
  - lib/httpx/io/tcp.rb
180
- - lib/httpx/io/tls.rb
181
- - lib/httpx/io/tls/box.rb
182
- - lib/httpx/io/tls/context.rb
183
- - lib/httpx/io/tls/ffi.rb
184
186
  - lib/httpx/io/udp.rb
185
187
  - lib/httpx/io/unix.rb
186
188
  - lib/httpx/loggable.rb
@@ -1,365 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2004-2013 Cotag Media
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is furnished
10
- # to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
- #
23
-
24
- class HTTPX::TLS
25
- class Box
26
- InstanceLookup = ::Concurrent::Map.new
27
-
28
- READ_BUFFER = 2048
29
- SSL_VERIFY_PEER = 0x01
30
- SSL_VERIFY_CLIENT_ONCE = 0x04
31
-
32
- VerifyCB = FFI::Function.new(:int, %i[int pointer]) do |preverify_ok, x509_store|
33
- x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
34
- ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
35
-
36
- bio_out = SSL.BIO_new(SSL.BIO_s_mem)
37
- ret = SSL.PEM_write_bio_X509(bio_out, x509)
38
- if ret
39
- len = SSL.BIO_pending(bio_out)
40
- buffer = FFI::MemoryPointer.new(:char, len, false)
41
- size = SSL.BIO_read(bio_out, buffer, len)
42
-
43
- # THis is the callback into the ruby class
44
- cert = buffer.read_string(size)
45
- SSL.BIO_free(bio_out)
46
- # InstanceLookup[ssl.address].verify(cert) || preverify_ok.zero? ? 1 : 0
47
- depth = SSL.X509_STORE_CTX_get_error_depth(ssl)
48
- box = InstanceLookup[ssl.address]
49
- if preverify_ok == 1
50
-
51
- hostname_verify = box.verify(cert)
52
- if hostname_verify
53
- 1
54
- else
55
- # SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_HOSTNAME_MISMATCH)
56
- 0
57
- end
58
- else
59
- 1
60
- end
61
- else
62
- SSL.BIO_free(bio_out)
63
- SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)
64
- 0
65
- end
66
- end
67
-
68
- attr_reader :is_server, :context, :handshake_completed, :hosts, :ssl_version, :cipher, :verify_peer
69
-
70
- def initialize(is_server, transport, options = {})
71
- @ready = true
72
-
73
- @handshake_completed = false
74
- @handshake_signaled = false
75
- @alpn_negotiated = false
76
- @transport = transport
77
-
78
- @read_buffer = FFI::MemoryPointer.new(:char, READ_BUFFER, false)
79
-
80
- @is_server = is_server
81
- @context = Context.new(is_server, options)
82
-
83
- @bioRead = SSL.BIO_new(SSL.BIO_s_mem)
84
- @bioWrite = SSL.BIO_new(SSL.BIO_s_mem)
85
- @ssl = SSL.SSL_new(@context.ssl_ctx)
86
- SSL.SSL_set_bio(@ssl, @bioRead, @bioWrite)
87
-
88
- @write_queue = []
89
-
90
- InstanceLookup[@ssl.address] = self
91
-
92
- @verify_peer = options[:verify_peer]
93
-
94
- if @verify_peer
95
- SSL.SSL_set_verify(@ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, VerifyCB)
96
- end
97
-
98
- # Add Server Name Indication (SNI) for client connections
99
- if (hostname = options[:hostname])
100
- if is_server
101
- @hosts = ::Concurrent::Map.new
102
- @hosts[hostname.to_s] = @context
103
- @context.add_server_name_indication
104
- else
105
- SSL.SSL_set_tlsext_host_name(@ssl, hostname)
106
- end
107
- end
108
-
109
- SSL.SSL_connect(@ssl) unless is_server
110
- end
111
-
112
- def add_host(hostname:, **options)
113
- raise Error, "Server Name Indication (SNI) not configured for default host" unless @hosts
114
- raise Error, "only valid for server mode context" unless @is_server
115
-
116
- context = Context.new(true, options)
117
- @hosts[hostname.to_s] = context
118
- context.add_server_name_indication
119
- nil
120
- end
121
-
122
- # Careful with this.
123
- # If you remove all the hosts you'll end up with a segfault
124
- def remove_host(hostname)
125
- raise Error, "Server Name Indication (SNI) not configured for default host" unless @hosts
126
- raise Error, "only valid for server mode context" unless @is_server
127
-
128
- context = @hosts[hostname.to_s]
129
- if context
130
- @hosts.delete(hostname.to_s)
131
- context.cleanup
132
- end
133
- nil
134
- end
135
-
136
- def get_peer_cert
137
- return "" unless @ready
138
-
139
- SSL.SSL_get_peer_certificate(@ssl)
140
- end
141
-
142
- def start
143
- return unless @ready
144
-
145
- dispatch_cipher_text
146
- end
147
-
148
- def encrypt(data)
149
- return unless @ready
150
-
151
- wrote = put_plain_text data
152
- if wrote < 0
153
- @transport.close_cb
154
- else
155
- dispatch_cipher_text
156
- end
157
- end
158
-
159
- SSL_ERROR_WANT_READ = 2
160
- SSL_ERROR_SSL = 1
161
- def decrypt(data)
162
- return unless @ready
163
-
164
- put_cipher_text data
165
-
166
- unless SSL.is_init_finished(@ssl)
167
- resp = @is_server ? SSL.SSL_accept(@ssl) : SSL.SSL_connect(@ssl)
168
-
169
- if resp < 0
170
- err_code = SSL.SSL_get_error(@ssl, resp)
171
- if err_code != SSL_ERROR_WANT_READ
172
- if err_code == SSL_ERROR_SSL
173
- verify_msg = SSL.X509_verify_cert_error_string(SSL.SSL_get_verify_result(@ssl))
174
- @transport.close_cb(verify_msg)
175
- end
176
- return
177
- end
178
- end
179
-
180
- @handshake_completed = true
181
- @ssl_version = SSL.get_version(@ssl)
182
- @cipher = SSL.get_current_cipher(@ssl)
183
- signal_handshake unless @handshake_signaled
184
- end
185
-
186
- loop do
187
- size = get_plain_text(@read_buffer, READ_BUFFER)
188
- if size > 0
189
- @transport.dispatch_cb @read_buffer.read_string(size)
190
- else
191
- break
192
- end
193
- end
194
-
195
- dispatch_cipher_text
196
- end
197
-
198
- def signal_handshake
199
- @handshake_signaled = true
200
-
201
- # Check protocol support here
202
- if @context.alpn_set
203
- proto = alpn_negotiated_protocol
204
-
205
- if proto == :failed
206
- if @alpn_negotiated
207
- # We should shutdown if this is the case
208
- # TODO: send back proper error message
209
- @transport.close_cb
210
- return
211
- end
212
- end
213
- @transport.alpn_protocol_cb(proto)
214
- end
215
-
216
- @transport.handshake_cb
217
- end
218
-
219
- def alpn_negotiated!
220
- @alpn_negotiated = true
221
- end
222
-
223
- SSL_RECEIVED_SHUTDOWN = 2
224
- def cleanup
225
- return unless @ready
226
-
227
- @ready = false
228
-
229
- InstanceLookup.delete @ssl.address
230
-
231
- if (SSL.SSL_get_shutdown(@ssl) & SSL_RECEIVED_SHUTDOWN) != 0
232
- SSL.SSL_shutdown @ssl
233
- else
234
- SSL.SSL_clear @ssl
235
- end
236
-
237
- SSL.SSL_free @ssl
238
-
239
- if @hosts
240
- @hosts.each_value(&:cleanup)
241
- @hosts = nil
242
- else
243
- @context.cleanup
244
- end
245
- end
246
-
247
- # Called from class level callback function
248
- def verify(cert)
249
- @transport.verify_cb(cert)
250
- end
251
-
252
- def close(msg)
253
- @transport.close_cb(msg)
254
- end
255
-
256
- private
257
-
258
- def alpn_negotiated_protocol
259
- return nil unless @context.alpn_set
260
-
261
- proto = FFI::MemoryPointer.new(:pointer, 1, true)
262
- len = FFI::MemoryPointer.new(:uint, 1, true)
263
- SSL.SSL_get0_alpn_selected(@ssl, proto, len)
264
-
265
- resp = proto.get_pointer(0)
266
-
267
- return :failed if resp.address == 0
268
-
269
- length = len.get_uint(0)
270
- resp.read_string(length)
271
- end
272
-
273
- def get_plain_text(buffer, ready)
274
- # Read the buffered clear text
275
- size = SSL.SSL_read(@ssl, buffer, ready)
276
- if size >= 0
277
- size
278
- else
279
- SSL.SSL_get_error(@ssl, size) == SSL_ERROR_WANT_READ ? 0 : -1
280
- end
281
- end
282
-
283
- def pending_data(bio)
284
- SSL.BIO_pending(bio)
285
- end
286
-
287
- def get_cipher_text(buffer, length)
288
- SSL.BIO_read(@bioWrite, buffer, length)
289
- end
290
-
291
- def put_cipher_text(data)
292
- len = data.bytesize
293
- wrote = SSL.BIO_write(@bioRead, data, len)
294
- wrote == len
295
- end
296
-
297
- SSL_ERROR_WANT_WRITE = 3
298
- def put_plain_text(data)
299
- @write_queue.push(data) if data
300
- return 0 unless SSL.is_init_finished(@ssl)
301
-
302
- fatal = false
303
- did_work = false
304
-
305
- until @write_queue.empty?
306
- data = @write_queue.pop
307
- len = data.bytesize
308
-
309
- wrote = SSL.SSL_write(@ssl, data, len)
310
-
311
- if wrote > 0
312
- did_work = true
313
- else
314
- err_code = SSL.SSL_get_error(@ssl, wrote)
315
- if (err_code != SSL_ERROR_WANT_READ) && (err_code != SSL_ERROR_WANT_WRITE)
316
- fatal = true
317
- else
318
- # Not fatal - add back to the queue
319
- @write_queue.unshift data
320
- end
321
-
322
- break
323
- end
324
- end
325
-
326
- if did_work
327
- 1
328
- elsif fatal
329
- -1
330
- else
331
- 0
332
- end
333
- end
334
-
335
- CIPHER_DISPATCH_FAILED = "Cipher text dispatch failed"
336
- def dispatch_cipher_text
337
- loop do
338
- did_work = false
339
-
340
- # Get all the encrypted data and transmit it
341
- pending = pending_data(@bioWrite)
342
- if pending > 0
343
- buffer = FFI::MemoryPointer.new(:char, pending, false)
344
-
345
- resp = get_cipher_text(buffer, pending)
346
- raise Error, CIPHER_DISPATCH_FAILED unless resp > 0
347
-
348
- @transport.transmit_cb(buffer.read_string(resp))
349
- did_work = true
350
- end
351
-
352
- # Send any queued out going data
353
- unless @write_queue.empty?
354
- resp = put_plain_text nil
355
- if resp > 0
356
- did_work = true
357
- elsif resp < 0
358
- @transport.close_cb
359
- end
360
- end
361
- break unless did_work
362
- end
363
- end
364
- end
365
- end