httpx 0.24.3 → 0.24.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: 145a6e293b5ae6bfc1008765a755ab823d490edf7c50bdbf1de01194e3e1537b
4
- data.tar.gz: 42d235a66fdb050a993b305d3aefed91893d26253cc62864ff2d0d088167ef0a
3
+ metadata.gz: bea174e31d4b4ca6122f591794da1096f14cf1ff058709c15a85adc3a871a011
4
+ data.tar.gz: 867912bad9a8161ee411a543de7932120f7eec975810c5150a308d004f7afaeb
5
5
  SHA512:
6
- metadata.gz: 7b10d4d91debc907d3445526ab864357a1e39cddfbe8b628f104241ed9015ae340e122dd49535be21bda7b20b95ebc047db4c476b7ff412e310d685f835e62c3
7
- data.tar.gz: 3d82a8bc8bc3d5e6f94c2dde6f330ab52a3a42e0cd95e4b62328e4b6b005aac9946d1903d384f0944112e997ca26801dc342d77b44290dddb8e88ec0523e1338
6
+ metadata.gz: 414ee34d56f3754b9109dfb7c20d2bfb9485fc070686993ad5271fcbfd4e5b01e3b7a56fa4ca18ad27435d4cc82ba8ec191975de4f42b44f8c03222775614996
7
+ data.tar.gz: 898850bfe5bf66b8d6b050eb1669cd4057417cdd9c320c0aaf962ab6a1c73958554452d67ed7a34e8f2d40b94910888aa70cd03589406bf0fab571e752c64ec7
data/README.md CHANGED
@@ -126,7 +126,7 @@ The plugin system is similar to the ones used by [sequel](https://github.com/jer
126
126
 
127
127
  ### Advanced DNS features
128
128
 
129
- `HTTPX` ships with custom DNS resolver implementations, including a native Happy Eyeballs resolver immplementation, and a DNS-over-HTTPS resolver.
129
+ `HTTPX` ships with custom DNS resolver implementations, including a native Happy Eyeballs resolver implementation, and a DNS-over-HTTPS resolver.
130
130
 
131
131
  ## User-driven test suite
132
132
 
@@ -0,0 +1,18 @@
1
+ # 0.24.4
2
+
3
+ ## Improvements
4
+
5
+ * `digest_authentication` plugin now supports passing HA1hashed with password HA1s (common to store in htdigest files for example) when setting the`:hashed` kwarg to `true` in the `.digest_auth` call.
6
+ * ex: `http.digest_auth(user, get_hashed_passwd_from_htdigest(user), hashed: true)`
7
+ * TLS session resumption is now supported
8
+ * whenever possible, `httpx` sessions will recycle used connections so that, in the case of TLS connections, the first session will keep being reusedd, thereby diminishing the overhead of subsequent TLS handshakes on the same host.
9
+ * TLS sessions are only reused in the scope of the same `httpx` session, unless the `:persistent` plugin is used, in which case, the persisted `httpx` session will always try to resume TLS sessions.
10
+
11
+ ## Bugfixess
12
+
13
+ * When explicitly using IP addresses in the URL host, TLS handshake will now verify tif he IP address is included in the certificate.
14
+ * IP address will keep not be used for SNI, as per RFC 6066, section 3.
15
+ * ex: `http.get("https://10.12.0.12/get")`
16
+ * if you want the prior behavior, set `HTTPX.with(ssl: {verify_hostname: faalse})`
17
+ * Turn TLS hostname verification on for `jruby` (it's turned off by default).
18
+ * if you want the prior behavior, set `HTTPX.with(ssl: {verify_hostname: faalse})`
@@ -42,7 +42,7 @@ module HTTPX
42
42
 
43
43
  def_delegator :@write_buffer, :empty?
44
44
 
45
- attr_reader :type, :io, :origin, :origins, :state, :pending, :options
45
+ attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
46
46
 
47
47
  attr_writer :timers
48
48
 
@@ -106,6 +106,12 @@ module HTTPX
106
106
  ) || (match_altsvcs?(uri) && match_altsvc_options?(uri, options))
107
107
  end
108
108
 
109
+ def expired?
110
+ return false unless @io
111
+
112
+ @io.expired?
113
+ end
114
+
109
115
  def mergeable?(connection)
110
116
  return false if @state == :closing || @state == :closed || !@io
111
117
 
@@ -138,6 +144,12 @@ module HTTPX
138
144
 
139
145
  def merge(connection)
140
146
  @origins |= connection.instance_variable_get(:@origins)
147
+ if connection.ssl_session
148
+ @ssl_session = connection.ssl_session
149
+ @io.session_new_cb do |sess|
150
+ @ssl_session = sess
151
+ end if @io
152
+ end
141
153
  connection.purge_pending do |req|
142
154
  send(req)
143
155
  end
@@ -280,6 +292,17 @@ module HTTPX
280
292
  @options.timeout[:operation_timeout]
281
293
  end
282
294
 
295
+ def idling
296
+ purge_after_closed
297
+ @write_buffer.clear
298
+ transition(:idle)
299
+ @parser = nil if @parser
300
+ end
301
+
302
+ def used?
303
+ @connected_at
304
+ end
305
+
283
306
  def deactivate
284
307
  transition(:inactive)
285
308
  end
@@ -310,6 +333,9 @@ module HTTPX
310
333
  catch(:called) do
311
334
  epiped = false
312
335
  loop do
336
+ # connection may have
337
+ return if @state == :idle
338
+
313
339
  parser.consume
314
340
 
315
341
  # we exit if there's no more requests to process
@@ -551,6 +577,7 @@ module HTTPX
551
577
  when :idle
552
578
  @timeout = @current_timeout = @options.timeout[:connect_timeout]
553
579
 
580
+ @connected_at = nil
554
581
  when :open
555
582
  return if @state == :closed
556
583
 
@@ -594,14 +621,23 @@ module HTTPX
594
621
  end
595
622
 
596
623
  def build_socket(addrs = nil)
597
- transport_type = case @type
598
- when "tcp" then TCP
599
- when "ssl" then SSL
600
- when "unix" then UNIX
601
- else
602
- raise Error, "unsupported transport (#{@type})"
624
+ case @type
625
+ when "tcp"
626
+ TCP.new(@origin, addrs, @options)
627
+ when "ssl"
628
+ SSL.new(@origin, addrs, @options) do |sock|
629
+ sock.ssl_session = @ssl_session
630
+ sock.session_new_cb do |sess|
631
+ @ssl_session = sess
632
+
633
+ sock.ssl_session = sess
634
+ end
635
+ end
636
+ when "unix"
637
+ UNIX.new(@origin, addrs, @options)
638
+ else
639
+ raise Error, "unsupported transport (#{@type})"
603
640
  end
604
- transport_type.new(@origin, addrs, @options)
605
641
  end
606
642
 
607
643
  def on_error(error)
data/lib/httpx/io/ssl.rb CHANGED
@@ -10,20 +10,48 @@ module HTTPX
10
10
  using RegexpExtensions unless Regexp.method_defined?(:match?)
11
11
 
12
12
  TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
13
- { alpn_protocols: %w[h2 http/1.1].freeze }.freeze
13
+ { alpn_protocols: %w[h2 http/1.1].freeze }
14
14
  else
15
- {}.freeze
15
+ {}
16
16
  end
17
+ # https://github.com/jruby/jruby-openssl/issues/284
18
+ TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby"
19
+ TLS_OPTIONS.freeze
20
+
21
+ attr_writer :ssl_session
17
22
 
18
23
  def initialize(_, _, options)
19
24
  super
20
- @ctx = OpenSSL::SSL::SSLContext.new
25
+
21
26
  ctx_options = TLS_OPTIONS.merge(options.ssl)
22
27
  @sni_hostname = ctx_options.delete(:hostname) || @hostname
23
- @ctx.set_params(ctx_options) unless ctx_options.empty?
24
- @state = :negotiated if @keep_open
28
+
29
+ if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
30
+ # externally initiated ssl socket
31
+ @ctx = @io.context
32
+ @state = :negotiated
33
+ else
34
+ @ctx = OpenSSL::SSL::SSLContext.new
35
+ @ctx.set_params(ctx_options) unless ctx_options.empty?
36
+ unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
37
+ @ctx.session_cache_mode =
38
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
39
+ end
40
+
41
+ yield(self) if block_given?
42
+ end
25
43
 
26
44
  @hostname_is_ip = IPRegex.match?(@sni_hostname)
45
+ @verify_hostname = @ctx.verify_hostname
46
+ end
47
+
48
+ if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
49
+ def session_new_cb(&pr)
50
+ @ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
51
+ end
52
+ else
53
+ # session_new_cb not implemented under JRuby
54
+ def session_new_cb; end
27
55
  end
28
56
 
29
57
  def protocol
@@ -43,25 +71,33 @@ module HTTPX
43
71
  OpenSSL::SSL.verify_certificate_identity(@io.peer_cert, host)
44
72
  end
45
73
 
46
- def close
47
- super
48
- # allow reconnections
49
- # connect only works if initial @io is a socket
50
- @io = @io.io if @io.respond_to?(:io)
51
- end
52
-
53
74
  def connected?
54
75
  @state == :negotiated
55
76
  end
56
77
 
78
+ def expired?
79
+ super || ssl_session_expired?
80
+ end
81
+
82
+ def ssl_session_expired?
83
+ @ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
84
+ end
85
+
57
86
  def connect
58
87
  super
59
88
  return if @state == :negotiated ||
60
89
  @state != :connected
61
90
 
62
91
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
92
+ if @hostname_is_ip
93
+ # IP addresses in SNI is not valid per RFC 6066, section 3.
94
+ @ctx.verify_hostname = false
95
+ end
96
+
63
97
  @io = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
98
+
64
99
  @io.hostname = @sni_hostname unless @hostname_is_ip
100
+ @io.session = @ssl_session unless ssl_session_expired?
65
101
  @io.sync_close = true
66
102
  end
67
103
  try_ssl_connect
@@ -71,7 +107,7 @@ module HTTPX
71
107
  # :nocov:
72
108
  def try_ssl_connect
73
109
  @io.connect_nonblock
74
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
110
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
75
111
  transition(:negotiated)
76
112
  @interests = :w
77
113
  rescue ::IO::WaitReadable
@@ -103,7 +139,7 @@ module HTTPX
103
139
  @interests = :w
104
140
  return
105
141
  end
106
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
142
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
107
143
  transition(:negotiated)
108
144
  @interests = :w
109
145
  end
@@ -130,6 +166,7 @@ module HTTPX
130
166
  case nextstate
131
167
  when :negotiated
132
168
  return unless @state == :connected
169
+
133
170
  when :closed
134
171
  return unless @state == :negotiated ||
135
172
  @state == :connected
data/lib/httpx/io/tcp.rb CHANGED
@@ -192,6 +192,17 @@ module HTTPX
192
192
  @state == :idle || @state == :closed
193
193
  end
194
194
 
195
+ def expired?
196
+ # do not mess with external sockets
197
+ return false if @options.io
198
+
199
+ return true unless @addresses
200
+
201
+ resolver_addresses = Resolver.nolookup_resolve(@hostname)
202
+
203
+ (Array(resolver_addresses) & @addresses).empty?
204
+ end
205
+
195
206
  # :nocov:
196
207
  def inspect
197
208
  "#<#{self.class}: #{@ip}:#{@port} (state: #{@state})>"
data/lib/httpx/io/unix.rb CHANGED
@@ -56,6 +56,10 @@ module HTTPX
56
56
  ::IO::WaitReadable
57
57
  end
58
58
 
59
+ def expired?
60
+ false
61
+ end
62
+
59
63
  # :nocov:
60
64
  def inspect
61
65
  "#<#{self.class}(path: #{@path}): (state: #{@state})>"
@@ -10,10 +10,11 @@ module HTTPX
10
10
  class Digest
11
11
  using RegexpExtensions unless Regexp.method_defined?(:match?)
12
12
 
13
- def initialize(user, password, **)
13
+ def initialize(user, password, hashed: false, **)
14
14
  @user = user
15
15
  @password = password
16
16
  @nonce = 0
17
+ @hashed = hashed
17
18
  end
18
19
 
19
20
  def can_authenticate?(authenticate)
@@ -55,11 +56,13 @@ module HTTPX
55
56
  end
56
57
 
57
58
  a1 = if sess
58
- [algorithm.hexdigest("#{@user}:#{params["realm"]}:#{@password}"),
59
- nonce,
60
- cnonce].join ":"
59
+ [
60
+ (@hashed ? @password : algorithm.hexdigest("#{@user}:#{params["realm"]}:#{@password}")),
61
+ nonce,
62
+ cnonce,
63
+ ].join ":"
61
64
  else
62
- "#{@user}:#{params["realm"]}:#{@password}"
65
+ @hashed ? @password : "#{@user}:#{params["realm"]}:#{@password}"
63
66
  end
64
67
 
65
68
  ha1 = algorithm.hexdigest(a1)
@@ -29,8 +29,8 @@ module HTTPX
29
29
  end
30
30
 
31
31
  module InstanceMethods
32
- def digest_authentication(user, password)
33
- with(digest: Authentication::Digest.new(user, password))
32
+ def digest_authentication(user, password, hashed: false)
33
+ with(digest: Authentication::Digest.new(user, password, hashed: hashed))
34
34
  end
35
35
 
36
36
  alias_method :digest_auth, :digest_authentication
data/lib/httpx/pool.rb CHANGED
@@ -17,6 +17,7 @@ module HTTPX
17
17
  @timers = Timers.new
18
18
  @selector = Selector.new
19
19
  @connections = []
20
+ @eden_connections = []
20
21
  @connected_connections = 0
21
22
  end
22
23
 
@@ -52,6 +53,7 @@ module HTTPX
52
53
  return if connections.empty?
53
54
 
54
55
  @timers.cancel
56
+ @eden_connections.clear
55
57
  connections = connections.reject(&:inflight?)
56
58
  connections.each(&:close)
57
59
  next_tick until connections.none? { |c| c.state != :idle && @connections.include?(c) }
@@ -97,9 +99,24 @@ module HTTPX
97
99
  # maximize pipelining by opening as few connections as possible.
98
100
  #
99
101
  def find_connection(uri, options)
100
- @connections.find do |connection|
102
+ conn = @connections.find do |connection|
101
103
  connection.match?(uri, options)
102
104
  end
105
+
106
+ unless conn
107
+ @eden_connections.delete_if do |connection|
108
+ is_expired = connection.expired?
109
+ conn = connection if conn.nil? && !is_expired && connection.match?(uri, options)
110
+ is_expired
111
+ end
112
+
113
+ if conn
114
+ @connections << conn
115
+ select_connection(conn)
116
+ end
117
+ end
118
+
119
+ conn
103
120
  end
104
121
 
105
122
  private
@@ -214,6 +231,10 @@ module HTTPX
214
231
 
215
232
  def unregister_connection(connection)
216
233
  @connections.delete(connection)
234
+ if connection.used? && !@eden_connections.include?(connection)
235
+ @eden_connections << connection
236
+ connection.idling
237
+ end
217
238
  @connected_connections -= 1 if deselect_connection(connection)
218
239
  end
219
240
 
@@ -301,7 +301,7 @@ module HTTPX
301
301
  end
302
302
  end
303
303
 
304
- return unless %i[memory buffer].include?(@state)
304
+ nil unless %i[memory buffer].include?(@state)
305
305
  end
306
306
 
307
307
  def _with_same_buffer_pos
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.24.3"
4
+ VERSION = "0.24.4"
5
5
  end
data/sig/connection.rbs CHANGED
@@ -48,6 +48,8 @@ module HTTPX
48
48
 
49
49
  def match?: (URI::Generic uri, Options options) -> bool
50
50
 
51
+ def expired?: () -> boolish
52
+
51
53
  def mergeable?: (Connection) -> bool
52
54
 
53
55
  def coalescable?: (Connection) -> bool
@@ -73,12 +75,17 @@ module HTTPX
73
75
  def call: () -> void
74
76
 
75
77
  def close: () -> void
78
+
76
79
  def reset: () -> void
77
80
 
78
81
  def send: (Request) -> void
79
82
 
80
83
  def timeout: () -> Numeric?
81
84
 
85
+ def idling: () -> void
86
+
87
+ def used?: () -> boolish
88
+
82
89
  def deactivate: () -> void
83
90
 
84
91
  def open?: () -> bool
data/sig/io/ssl.rbs CHANGED
@@ -1,16 +1,24 @@
1
1
  module HTTPX
2
2
  IPRegex: Regexp
3
3
 
4
+ @ctx: OpenSSL::SSL::SSLContext
5
+ @verify_hostname: bool
6
+
4
7
  class TLSError < OpenSSL::SSL::SSLError
5
8
  end
6
9
 
7
10
  class SSL < TCP
8
11
  TLS_OPTIONS: Hash[Symbol, untyped]
9
12
 
13
+ # TODO: lift when https://github.com/ruby/rbs/issues/1497 fixed
14
+ # def initialize: (URI::Generic origin, Array[ipaddr]? addresses, options options) ?{ (self) -> void } -> void
15
+
10
16
  def can_verify_peer?: () -> bool
11
17
 
12
18
  def verify_hostname: (String host) -> bool
13
19
 
20
+ def ssl_session_expired?: () -> boolish
21
+
14
22
  # :nocov:
15
23
  def try_ssl_connect: () -> void
16
24
  end
data/sig/io/tcp.rbs CHANGED
@@ -14,7 +14,8 @@ module HTTPX
14
14
 
15
15
  alias host ip
16
16
 
17
- def initialize: (URI::Generic origin, Array[ipaddr]? addresses, options options) -> void
17
+ # TODO: lift when https://github.com/ruby/rbs/issues/1497 fixed
18
+ def initialize: (URI::Generic origin, Array[ipaddr]? addresses, options options) ?{ (self) -> void } -> void
18
19
 
19
20
  def add_addresses: (Array[ipaddr] addrs) -> void
20
21
 
@@ -39,6 +40,8 @@ module HTTPX
39
40
 
40
41
  def connected?: () -> bool
41
42
 
43
+ def expired?: () -> boolish
44
+
42
45
  def closed?: () -> bool
43
46
 
44
47
  # :nocov:
@@ -4,6 +4,7 @@ module HTTPX
4
4
  class Digest
5
5
  @user: String
6
6
  @password: String
7
+ @hashed: bool
7
8
 
8
9
  def can_authenticate?: (String? authenticate) -> boolish
9
10
 
@@ -13,7 +14,7 @@ module HTTPX
13
14
 
14
15
  def generate_header: (String meth, String uri, String authenticate) -> String
15
16
 
16
- def initialize: (string user, string password) -> void
17
+ def initialize: (string user, string password, ?hashed: bool, **untyped) -> void
17
18
 
18
19
  def make_cnonce: () -> String
19
20
 
@@ -12,7 +12,7 @@ module HTTPX
12
12
  def self.load_dependencies: (*untyped) -> void
13
13
 
14
14
  module InstanceMethods
15
- def digest_authentication: (string user, string password) -> instance
15
+ def digest_authentication: (string user, string password, ?hashed: bool) -> instance
16
16
  end
17
17
  end
18
18
 
data/sig/pool.rbs CHANGED
@@ -6,6 +6,7 @@ module HTTPX
6
6
  @timers: Timers
7
7
  @selector: Selector
8
8
  @connections: Array[Connection]
9
+ @eden_connections: Array[Connection]
9
10
  @connected_connections: Integer
10
11
 
11
12
  def empty?: () -> 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.24.3
4
+ version: 0.24.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: 2023-07-31 00:00:00.000000000 Z
11
+ date: 2023-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -103,6 +103,7 @@ extra_rdoc_files:
103
103
  - doc/release_notes/0_24_1.md
104
104
  - doc/release_notes/0_24_2.md
105
105
  - doc/release_notes/0_24_3.md
106
+ - doc/release_notes/0_24_4.md
106
107
  - doc/release_notes/0_2_0.md
107
108
  - doc/release_notes/0_2_1.md
108
109
  - doc/release_notes/0_3_0.md
@@ -198,6 +199,7 @@ files:
198
199
  - doc/release_notes/0_24_1.md
199
200
  - doc/release_notes/0_24_2.md
200
201
  - doc/release_notes/0_24_3.md
202
+ - doc/release_notes/0_24_4.md
201
203
  - doc/release_notes/0_2_0.md
202
204
  - doc/release_notes/0_2_1.md
203
205
  - doc/release_notes/0_3_0.md