amq-protocol 2.7.0 → 2.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c88e7605bb8cf664993429123aab42370d5f8772f6ae70792ad2e64fd16051a
4
- data.tar.gz: 74c708d7dcf7fa7a136728e845b306b7ee4d5ce56c248cbaf24659d3a3de4b9b
3
+ metadata.gz: 26124906b35c821e2d416aee1eac743185d3419e11adc00650811afbdc505153
4
+ data.tar.gz: 351ddaddb2d40f50fa758813548cae68f73142c3a2e4c2409e63e12a498d9e01
5
5
  SHA512:
6
- metadata.gz: d6a6442583dfdb00958f6860df51a0fdfea871ff7defb3a459bbffd8e197217e6b611a034466ea41b0a2adb1203c4b873f9e81a60b27858dd4498f105eda6e48
7
- data.tar.gz: 8859c61e44da33a17515ac51953178f2d55824b230677da2d750c8acc9ee0a3818a858800ba705a8a2c6115709375f7777970a0506acc61847362a7871b7fd8d
6
+ metadata.gz: f052ec8349b5d783e191b80301ab4d517ee3521be7643eb150805ead5c889fc4e6db05c7883b9a510662b6c6846539a5b2fe6ff7a3372ead22b6ddb9c2db7476
7
+ data.tar.gz: 1fe1329bd16dd74002277aba7f796533b6206df8125d307b7668f7132c0200b8398b1aedb9ce5c732fc71fad5413e82fa2fb3b9f938062652b35ac9efe4e964a
data/ChangeLog.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## Changes between 2.8.0 and 2.9.0 (Aug 26, 2026)
2
+
3
+ ### amq-protocol Now Requires Ruby 3.2
4
+
5
+ Ruby 3.0 and 3.1 are end of life. The URI parser now uses
6
+ `URI.decode_uri_component`, which Ruby 3.2 provides.
7
+
8
+
9
+ ### URI Components Are Percent-Decoded, Not Form-Decoded
10
+
11
+ `AMQ::URI.parse` decoded the username, password and virtual host with
12
+ `CGI.unescape`, so a password such as `pa+ss` was parsed as `pa ss` and failed
13
+ to authenticate. They are now percent-decoded; use `%20` where a `+` used to
14
+ mean a space.
15
+
16
+ Contributed by @eglitobias.
17
+
18
+ GitHub issue: [#89](https://github.com/ruby-amqp/amq-protocol/pull/89)
19
+
20
+
21
+ ## Changes between 2.7.0 and 2.8.0 (Apr 25, 2026)
22
+
23
+ ### Performance Improvements in Frame Decoding and Encoding
24
+
25
+ Replacing `x == nil` with `x.nil?` in the frame layer hot path yields a
26
+ consistent **+15–18% throughput improvement** in `Frame.decode_header`
27
+ (called on every received frame) and **+12–14%** in `HeartbeatFrame.encode`,
28
+ across Ruby 3.3, 3.4, and 4.0.
29
+
30
+ See benchmarks/BENCHMARKS.md for instructions on how to reproduce these numbers on your machine.
31
+
32
+ Contributed by @eglitobias.
33
+
34
+ GitHub issue: [#86](https://github.com/ruby-amqp/amq-protocol/pull/86)
35
+
36
+
1
37
  ## Changes between 2.6.0 and 2.7.0 (Mar 31, 2026)
2
38
 
3
39
  ### Channel.Close Predicates Now Return True Boolean
@@ -42,7 +78,7 @@ Optimized encoding and decoding hot paths:
42
78
  * Switched to `unpack1` instead of `unpack().first` throughout
43
79
  * Use `byteslice` instead of `slice` for binary string operations
44
80
  * Use `getbyte` for single byte access (4x faster than alternatives)
45
- * Adopted `frozen_string_literal` pragma
81
+ * Adopted the `frozen_string_literal` pragma
46
82
 
47
83
  The improvements on Ruby 3.4 are very meaningful:
48
84
 
data/README.md CHANGED
@@ -9,6 +9,8 @@ needs for you, including RabbitMQ extensions to AMQP 0.9.1.
9
9
 
10
10
  ## Supported Ruby Versions
11
11
 
12
+ * amq-protocol `2.9.0` requires Ruby 3.2 or later
13
+ * amq-protocol `2.8.0` requires Ruby 3.0 or later
12
14
  * amq-protocol `2.3.4` has fixes for Ruby 3.5 compatibility
13
15
  * amq-protocol `2.3.3` has fixes for Ruby 3.4 compatibility
14
16
  * amq-protocol `2.3.0` only supports Ruby 2.2+.
data/lib/amq/bit_set.rb CHANGED
@@ -113,7 +113,7 @@ module AMQ
113
113
 
114
114
  def check_range(i)
115
115
  if i < 0 || i >= @nbits
116
- raise IndexError.new("Cannot access bit #{i} from a BitSet with #{@nbits} bits")
116
+ raise IndexError, "Cannot access bit #{i} from a BitSet with #{@nbits} bits"
117
117
  end
118
118
  end # check_range
119
119
  end # BitSet
@@ -26,13 +26,12 @@ module AMQ
26
26
  # @param [Integer] hi Upper boundary of the integer range available for allocation
27
27
  # @raise [ArgumentError] if upper boundary is not greater than the lower one
28
28
  def initialize(lo, hi)
29
- raise ArgumentError.new "upper boundary must be greater than the lower one (given: hi = #{hi}, lo = #{lo})" unless hi > lo
29
+ raise ArgumentError, "upper boundary must be greater than the lower one (given: hi = #{hi}, lo = #{lo})" unless hi > lo
30
30
 
31
31
  @hi = hi
32
32
  @lo = lo
33
33
 
34
34
  @number_of_bits = hi - lo
35
- @range = Range.new(1, @number_of_bits)
36
35
  @free_set = BitSet.new(@number_of_bits)
37
36
  end # initialize(hi, lo)
38
37
 
@@ -47,7 +46,7 @@ module AMQ
47
46
 
48
47
  if n = @free_set.next_clear_bit
49
48
 
50
- if n < @hi - 1 then
49
+ if n < @hi - 1
51
50
  @free_set.set(n)
52
51
  n + 1
53
52
  else
@@ -173,9 +173,7 @@ module AMQ
173
173
  limit = frame_size - 8
174
174
  return [BodyFrame.new(body, channel)] if body.bytesize < limit
175
175
 
176
- # Otherwise String#slice on 1.9 will operate with code points,
177
- # and we need bytes. MK.
178
- body.force_encoding("ASCII-8BIT") if RUBY_VERSION.to_f >= 1.9 && body.encoding != Encoding::BINARY
176
+ body.force_encoding("ASCII-8BIT") if body.encoding != Encoding::BINARY
179
177
 
180
178
  array = Array.new
181
179
  while body && !body.empty?
@@ -23,8 +23,8 @@ module AMQ
23
23
 
24
24
  # The channel number is 0 for all frames which are global to the connection and 1-65535 for frames that refer to specific channels.
25
25
  def self.encode_to_array(type, payload, channel)
26
- raise RuntimeError.new("Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}") unless CHANNEL_RANGE.include?(channel)
27
- raise RuntimeError.new("Payload can't be nil") if payload.nil?
26
+ raise RuntimeError, "Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}" unless CHANNEL_RANGE.include?(channel)
27
+ raise RuntimeError, "Payload can't be nil" if payload.nil?
28
28
  components = []
29
29
  components << [find_type(type), channel, payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
30
30
  components << encoded_payload(payload)
@@ -47,13 +47,13 @@ module AMQ
47
47
  end
48
48
 
49
49
  def self.find_type(type)
50
- type_id = if Symbol === type then TYPES[type] else type end
51
- raise FrameTypeError.new(TYPES_OPTIONS) if type == nil || !TYPES_REVERSE.has_key?(type_id)
50
+ type_id = type.is_a?(Symbol) ? TYPES[type] : type
51
+ raise FrameTypeError, TYPES_OPTIONS if type.nil? || !TYPES_REVERSE.key?(type_id)
52
52
  type_id
53
53
  end
54
54
 
55
55
  def self.decode(*)
56
- raise NotImplementedError.new <<-EOF
56
+ raise NotImplementedError, <<-EOF
57
57
  You are supposed to redefine this method, because it's dependent on used IO adapter.
58
58
 
59
59
  This functionality is part of the https://github.com/ruby-amqp/amq-client library.
@@ -62,12 +62,12 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
62
62
 
63
63
  # Optimized header decode using unpack1 for single values where appropriate
64
64
  def self.decode_header(header)
65
- raise EmptyResponseError if header == nil || header.empty?
65
+ raise EmptyResponseError if header.nil? || header.empty?
66
66
 
67
67
  # Use unpack for multiple values - this is the optimal approach
68
68
  type_id, channel, size = header.unpack(PACK_CHAR_UINT16_UINT32)
69
69
  type = TYPES_REVERSE[type_id]
70
- raise FrameTypeError.new(TYPES_OPTIONS) unless type
70
+ raise FrameTypeError, TYPES_OPTIONS unless type
71
71
  [type, channel, size]
72
72
  end
73
73
 
@@ -83,7 +83,7 @@ module AMQ
83
83
  v, offset = decode_array(data, offset)
84
84
  v
85
85
  else
86
- raise ArgumentError.new("unsupported type in a table value: #{type.inspect}, do not know how to decode!")
86
+ raise ArgumentError, "unsupported type in a table value: #{type.inspect}, do not know how to decode!"
87
87
  end
88
88
 
89
89
  ary << i
@@ -72,7 +72,7 @@ module AMQ
72
72
  accumulator << [0, value.to_i].pack(PACK_UCHAR_UINT32)
73
73
  end
74
74
  else
75
- raise ArgumentError.new("Unsupported value #{value.inspect} of type #{value.class.name}")
75
+ raise ArgumentError, "Unsupported value #{value.inspect} of type #{value.class.name}"
76
76
  end # if
77
77
  end # case
78
78
 
@@ -1,5 +1,5 @@
1
1
  module AMQ
2
2
  module Protocol
3
- VERSION = "2.7.0"
3
+ VERSION = "2.9.0"
4
4
  end # Protocol
5
5
  end # AMQ
data/lib/amq/uri.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- if RUBY_VERSION < "3.5"
4
- require "cgi/util"
5
- else
6
- require "cgi/escape"
7
- end
8
3
  require "uri"
9
4
 
10
5
  module AMQ
@@ -31,19 +26,23 @@ module AMQ
31
26
 
32
27
  def self.parse(connection_string)
33
28
  uri = ::URI.parse(connection_string)
34
- raise ArgumentError.new("Connection URI must use amqp or amqps schema (example: amqp://bus.megacorp.internal:5766), learn more at http://bit.ly/ks8MXK") unless %w{amqp amqps}.include?(uri.scheme)
29
+ unless %w{amqp amqps}.include?(uri.scheme)
30
+ raise ArgumentError, "Connection URI must use amqp or amqps schema (example: amqp://bus.megacorp.internal:5766), learn more at http://bit.ly/ks8MXK"
31
+ end
35
32
 
36
33
  opts = DEFAULTS.dup
37
34
 
38
35
  opts[:scheme] = uri.scheme
39
- opts[:user] = ::CGI::unescape(uri.user) if uri.user
40
- opts[:pass] = ::CGI::unescape(uri.password) if uri.password
36
+ opts[:user] = ::URI.decode_uri_component(uri.user) if uri.user
37
+ opts[:pass] = ::URI.decode_uri_component(uri.password) if uri.password
41
38
  opts[:host] = uri.host if uri.host and uri.host != ""
42
39
  opts[:port] = uri.port || AMQP_DEFAULT_PORTS[uri.scheme]
43
40
  opts[:ssl] = uri.scheme.to_s.downcase =~ /amqps/i # TODO: rename to tls
44
41
  if uri.path =~ %r{^/(.*)}
45
- raise ArgumentError.new("#{uri} has multiple-segment path; please percent-encode any slashes in the vhost name (e.g. /production => %2Fproduction). Learn more at http://bit.ly/amqp-gem-and-connection-uris") if $1.index('/')
46
- opts[:vhost] = ::CGI::unescape($1)
42
+ if $1.index('/')
43
+ raise ArgumentError, "#{uri} has multiple-segment path; please percent-encode any slashes in the vhost name (e.g. /production => %2Fproduction). Learn more at http://bit.ly/amqp-gem-and-connection-uris"
44
+ end
45
+ opts[:vhost] = ::URI.decode_uri_component($1)
47
46
  end
48
47
 
49
48
  if uri.query
@@ -63,7 +62,7 @@ module AMQ
63
62
 
64
63
  %w(cacertfile certfile keyfile).each do |tls_option|
65
64
  if query_params[tls_option] && uri.scheme == "amqp"
66
- raise ArgumentError.new("The option '#{tls_option}' can only be used in URIs that use amqps schema")
65
+ raise ArgumentError, "The option '#{tls_option}' can only be used in URIs that use amqps schema"
67
66
  else
68
67
  opts[tls_option.to_sym] = query_params[tls_option]
69
68
  end
@@ -71,7 +70,7 @@ module AMQ
71
70
 
72
71
  %w(verify fail_if_no_peer_cert).each do |tls_option|
73
72
  if query_params[tls_option] && uri.scheme == "amqp"
74
- raise ArgumentError.new("The option '#{tls_option}' can only be used in URIs that use amqps schema")
73
+ raise ArgumentError, "The option '#{tls_option}' can only be used in URIs that use amqps schema"
75
74
  else
76
75
  opts[tls_option.to_sym] = as_boolean(query_params[tls_option])
77
76
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amq-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Stastny
@@ -43,7 +43,7 @@ files:
43
43
  - lib/amq/protocol/version.rb
44
44
  - lib/amq/settings.rb
45
45
  - lib/amq/uri.rb
46
- homepage: http://github.com/ruby-amqp/amq-protocol
46
+ homepage: https://github.com/ruby-amqp/amq-protocol
47
47
  licenses:
48
48
  - MIT
49
49
  metadata: {}
@@ -54,14 +54,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: '2.2'
57
+ version: '3.2'
58
58
  required_rubygems_version: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  requirements: []
64
- rubygems_version: 3.6.7
64
+ rubygems_version: 4.0.16
65
65
  specification_version: 4
66
66
  summary: AMQP 0.9.1 encoding & decoding library.
67
67
  test_files: []