bbk-amqp 1.0.0.72904

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e0df2e2301de0e3ec42d3f12ce60b93321e206f0c1263318af185ca33487046
4
+ data.tar.gz: 371aa962df0e67a3215c551ada7f9538969d6662607ee812ad3cf1eb4ded83c6
5
+ SHA512:
6
+ metadata.gz: 777ec22b1895c6b86bee77be39764b4b0f66558edaad57c26feb425fce556050fa15a75ef136368b6de79f8261475e40dfb6e2c4717c43481658c5eb4a019652
7
+ data.tar.gz: e06609cb88278b16c1ce1c9fe341d0694c492915fbdedc1c99e54fa1a3d0aeb9a169bc2e20e8b9ea4f511d6d1a97a3b46bab598d420eb2dacfa4030928f03928
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in bbk-amqp.gemspec
6
+ gemspec
7
+
data/Gemfile.lock ADDED
@@ -0,0 +1,154 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bbk-amqp (1.0.0.72904)
5
+ activesupport (~> 6.0)
6
+ bbk-utils (> 1.0.1)
7
+ bunny (>= 2.19.0)
8
+ oj
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (6.1.4.4)
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ i18n (>= 1.6, < 2)
16
+ minitest (>= 5.1)
17
+ tzinfo (~> 2.0)
18
+ zeitwerk (~> 2.3)
19
+ addressable (2.8.0)
20
+ public_suffix (>= 2.0.2, < 5.0)
21
+ amq-protocol (2.3.2)
22
+ ansi (1.5.0)
23
+ ast (2.4.2)
24
+ axiom-types (0.1.1)
25
+ descendants_tracker (~> 0.0.4)
26
+ ice_nine (~> 0.11.0)
27
+ thread_safe (~> 0.3, >= 0.3.1)
28
+ bbk-app (1.0.0.72899)
29
+ activesupport
30
+ bbk-utils (> 1.0.1)
31
+ timeouter
32
+ bbk-utils (1.0.1.72735)
33
+ activesupport (~> 6.0)
34
+ russian
35
+ bunny (2.19.0)
36
+ amq-protocol (~> 2.3, >= 2.3.1)
37
+ sorted_set (~> 1, >= 1.0.2)
38
+ bunny-mock (1.7.0)
39
+ bunny (>= 1.7)
40
+ byebug (11.1.3)
41
+ coercible (1.0.0)
42
+ descendants_tracker (~> 0.0.1)
43
+ concurrent-ruby (1.1.9)
44
+ descendants_tracker (0.0.4)
45
+ thread_safe (~> 0.3, >= 0.3.1)
46
+ diff-lcs (1.5.0)
47
+ docile (1.4.0)
48
+ equalizer (0.0.11)
49
+ erubis (2.7.0)
50
+ flay (2.12.1)
51
+ erubis (~> 2.7.0)
52
+ path_expander (~> 1.0)
53
+ ruby_parser (~> 3.0)
54
+ sexp_processor (~> 4.0)
55
+ flog (4.6.4)
56
+ path_expander (~> 1.0)
57
+ ruby_parser (~> 3.1, > 3.1.0)
58
+ sexp_processor (~> 4.8)
59
+ i18n (1.8.11)
60
+ concurrent-ruby (~> 1.0)
61
+ ice_nine (0.11.2)
62
+ kwalify (0.7.2)
63
+ launchy (2.5.0)
64
+ addressable (~> 2.7)
65
+ minitest (5.15.0)
66
+ oj (3.13.11)
67
+ parser (3.0.3.2)
68
+ ast (~> 2.4.1)
69
+ path_expander (1.1.0)
70
+ public_suffix (4.0.6)
71
+ rainbow (3.1.1)
72
+ rake (13.0.6)
73
+ rbtree (0.4.4)
74
+ reek (6.0.6)
75
+ kwalify (~> 0.7.0)
76
+ parser (~> 3.0.0)
77
+ rainbow (>= 2.0, < 4.0)
78
+ rspec (3.10.0)
79
+ rspec-core (~> 3.10.0)
80
+ rspec-expectations (~> 3.10.0)
81
+ rspec-mocks (~> 3.10.0)
82
+ rspec-core (3.10.1)
83
+ rspec-support (~> 3.10.0)
84
+ rspec-expectations (3.10.1)
85
+ diff-lcs (>= 1.2.0, < 2.0)
86
+ rspec-support (~> 3.10.0)
87
+ rspec-mocks (3.10.2)
88
+ diff-lcs (>= 1.2.0, < 2.0)
89
+ rspec-support (~> 3.10.0)
90
+ rspec-support (3.10.3)
91
+ rspec_junit_formatter (0.5.1)
92
+ rspec-core (>= 2, < 4, != 2.12.0)
93
+ ruby_parser (3.18.1)
94
+ sexp_processor (~> 4.16)
95
+ rubycritic (4.6.1)
96
+ flay (~> 2.8)
97
+ flog (~> 4.4)
98
+ launchy (>= 2.0.0)
99
+ parser (>= 2.6.0)
100
+ rainbow (~> 3.0)
101
+ reek (~> 6.0, < 7.0)
102
+ ruby_parser (~> 3.8)
103
+ simplecov (>= 0.17.0)
104
+ tty-which (~> 0.4.0)
105
+ virtus (~> 1.0)
106
+ russian (0.6.0)
107
+ i18n (>= 0.5.0)
108
+ set (1.0.2)
109
+ sexp_processor (4.16.0)
110
+ simplecov (0.21.2)
111
+ docile (~> 1.1)
112
+ simplecov-html (~> 0.11)
113
+ simplecov_json_formatter (~> 0.1)
114
+ simplecov-console (0.9.1)
115
+ ansi
116
+ simplecov
117
+ terminal-table
118
+ simplecov-html (0.12.3)
119
+ simplecov_json_formatter (0.1.3)
120
+ sorted_set (1.0.3)
121
+ rbtree
122
+ set (~> 1.0)
123
+ terminal-table (3.0.2)
124
+ unicode-display_width (>= 1.1.1, < 3)
125
+ thread_safe (0.3.6)
126
+ timeouter (0.1.3.38794)
127
+ tty-which (0.4.2)
128
+ tzinfo (2.0.4)
129
+ concurrent-ruby (~> 1.0)
130
+ unicode-display_width (2.1.0)
131
+ virtus (1.0.5)
132
+ axiom-types (~> 0.1)
133
+ coercible (~> 1.0)
134
+ descendants_tracker (~> 0.0, >= 0.0.3)
135
+ equalizer (~> 0.0, >= 0.0.9)
136
+ zeitwerk (2.5.3)
137
+
138
+ PLATFORMS
139
+ ruby
140
+
141
+ DEPENDENCIES
142
+ bbk-amqp!
143
+ bbk-app (>= 1.0.0)
144
+ bunny-mock
145
+ byebug
146
+ rake
147
+ rspec
148
+ rspec_junit_formatter
149
+ rubycritic
150
+ simplecov
151
+ simplecov-console
152
+
153
+ BUNDLED WITH
154
+ 2.2.33
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # BBK::AMQP
2
+
3
+ AMQP interaction classes for BBK stack.
4
+
5
+ ## Installation
6
+
7
+ Adding to a gem:
8
+
9
+ ```ruby
10
+ # my-cool-gem.gemspec
11
+
12
+ Gem::Specification.new do |spec|
13
+ # ...
14
+ spec.add_dependency "bbk-amqp", "~> 1.0.0"
15
+ # ...
16
+ end
17
+ ```
18
+
19
+ Or adding to your project:
20
+
21
+ ```ruby
22
+ # Gemfile
23
+
24
+ gem "bbk-amqp", "~> 1.0.0"
25
+ ```
26
+
27
+ ### Supported Ruby versions
28
+
29
+ * Ruby (MRI) >= 2.5.0
30
+
31
+ ### Tested Ruby versions
32
+
33
+ * Ruby (MRI) 2.5.x
34
+ * Ruby (MRI) 3.0.x
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the MIT License.
data/bin/console ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'bbk/amqp'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
16
+
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BBK
4
+ module AMQP
5
+ class Consumer
6
+
7
+ attr_reader :connection, :queue_name, :queue, :options, :logger
8
+
9
+ DEFAULT_OPTIONS = {
10
+ consumer_pool_size: 3,
11
+ consumer_pool_abort_on_exception: true,
12
+ prefetch_size: 10,
13
+ requeue_on_reject: false,
14
+ consumer_tag: nil
15
+ }.freeze
16
+
17
+ PROTOCOLS = %w[mq amqp amqps].freeze
18
+
19
+ def initialize(connection, queue_name: nil, **options)
20
+ @connection = connection
21
+ @channel = options.delete(:channel)
22
+ @queue = options.delete(:queue)
23
+
24
+ if @queue.nil? && queue_name.nil?
25
+ raise ArgumentError.new('queue_name or queue must be provided!')
26
+ end
27
+
28
+ @queue_name = @queue&.name || queue_name
29
+
30
+ @options = DEFAULT_OPTIONS.merge(options)
31
+
32
+ logger = @options.fetch(:logger, BBK::AMQP.logger)
33
+ logger = logger.respond_to?(:tagged) ? logger : ActiveSupport::TaggedLogging.new(logger)
34
+ @logger = BBK::Utils::ProxyLogger.new(logger, tags: [self.class.to_s, queue_name])
35
+ end
36
+
37
+ # Return protocol list which consumer support
38
+ def protocols
39
+ PROTOCOLS
40
+ end
41
+
42
+ # Running non blocking consumer
43
+ # @param msg_stream [Enumerable] - object with << method
44
+ def run(msg_stream)
45
+ @channel ||= @connection.create_channel(nil, options[:consumer_pool_size],
46
+ options[:consumer_pool_abort_on_exception]).tap do |ch|
47
+ ch.prefetch(options[:prefetch_size])
48
+ end
49
+
50
+ @logger.add_tags "Ch##{@channel.id}"
51
+
52
+ @queue ||= @channel.queue(queue_name, passive: true)
53
+
54
+ subscribe_opts = {
55
+ block: false,
56
+ manual_ack: true,
57
+ consumer_tag: options[:consumer_tag]
58
+ }.compact
59
+
60
+ logger.info 'Starting...'
61
+ @subscription = queue.subscribe(subscribe_opts) do |delivery_info, metadata, payload|
62
+ message = Message.new(self, delivery_info, metadata, payload)
63
+ # logger.debug "Consumed message #{message.headers[:type]}[#{message.headers[:message_id]}] on channel: #{delivery_info.channel&.id}[#{delivery_info.channel&.object_id}] delivery tag: #{message.delivery_info[:delivery_tag].to_i}"
64
+ logger.debug "Consumed message #{message.headers[:type]}[#{message.headers[:message_id]}] delivery tag: #{message.delivery_info[:delivery_tag].to_i}"
65
+
66
+ msg_stream << message
67
+ end
68
+ msg_stream
69
+ end
70
+
71
+ # Ack incoming message and not send answer.
72
+ # @note answer should processing amqp publisher
73
+ # @param incoming [BBK::AMQP::Message] consumed message from amqp channel
74
+ # @param answer [BBK::App::Dispatcher::Result] answer message
75
+ def ack(incoming, *args, answer: nil, **kwargs)
76
+ # [] - для работы тестов. В реальности вернется объект VersionedDeliveryTag у
77
+ # которого to_i (вызывается внутри channel.ack) вернет фактическоe число
78
+ # logger.debug "Ack message #{incoming.headers[:type]}[#{incoming.headers[:message_id]}] on channel: #{incoming.delivery_info[:channel]&.id}[#{incoming.delivery_info[:channel]&.object_id}] delivery tag: #{incoming.delivery_info[:delivery_tag].to_i}"
79
+ logger.debug "Ack message #{incoming.headers[:type]}[#{incoming.headers[:message_id]}] delivery tag: #{incoming.delivery_info[:delivery_tag].to_i}"
80
+ incoming.delivery_info[:channel].ack incoming.delivery_info[:delivery_tag]
81
+ end
82
+
83
+ # Nack incoming message
84
+ # @param incoming [BBK::AMQP::Message] nack procesing message
85
+ def nack(incoming, *args, error: nil, requeue: nil, **_kwargs)
86
+ logger.debug "Reject message #{incoming.headers[:type]}[#{incoming.headers[:message_id]}] delivery tag: #{incoming.delivery_info[:delivery_tag].to_i}. Error: #{error.inspect}"
87
+ requeue_message = requeue.nil? ? options[:requeue_on_reject] : requeue
88
+ incoming.delivery_info[:channel].reject incoming.delivery_info[:delivery_tag],
89
+ requeue_message
90
+ end
91
+
92
+ # stop consuming messages
93
+ def stop
94
+ @subscription.tap do |s|
95
+ return nil unless s
96
+
97
+ logger.info 'Stopping...'
98
+ @subscription = nil
99
+ s.cancel
100
+ end
101
+ end
102
+
103
+ # Close consumer - try close amqp channel
104
+ def close
105
+ @channel.tap do |c|
106
+ return nil unless c
107
+
108
+ logger.info 'Closing...'
109
+ @channel = nil
110
+ c.close
111
+ logger.info 'Stopped'
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
118
+
@@ -0,0 +1,24 @@
1
+ module BBK
2
+ module AMQP
3
+ module Domains
4
+ class ByBlock
5
+
6
+ attr_reader :name
7
+
8
+ def initialize(name, &block)
9
+ raise ArgumentError.new('no block') unless block_given?
10
+
11
+ @name = name
12
+ @block = block
13
+ end
14
+
15
+ def call(route)
16
+ @block.call(route)
17
+ end
18
+
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,22 @@
1
+ module BBK
2
+ module AMQP
3
+ module Domains
4
+ class Exchange
5
+
6
+
7
+ attr_reader :name, :exchange
8
+
9
+ def initialize(name, exchange)
10
+ @name = name
11
+ @exchange = exchange
12
+ end
13
+
14
+ def call(route)
15
+ BBK::AMQP::RouteInfo.new(exchange, route.routing_key)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BBK
4
+ module AMQP
5
+ # Store for amqp domains. Domain is pair: domain name and exchange name.
6
+ class DomainsSet
7
+
8
+ def initialize(*domains)
9
+ @domains = domains.map{|d| [d.name.to_s, d] }.to_h
10
+ end
11
+
12
+ # Get exchange name by domain
13
+ # @param domain_name [String] domain name
14
+ # @return [String] exchange name configured for passed domain name
15
+ def [](domain_name)
16
+ @domains[domain_name]
17
+ end
18
+
19
+ def add(domain)
20
+ @domains[domain.name.to_s] = domain
21
+ end
22
+
23
+ alias << add
24
+
25
+ # Each method implementation for object iteration
26
+ def each(&block)
27
+ @domains.values.each(&block)
28
+ end
29
+
30
+ # Check if store has information about domain
31
+ # @param domain_name [String] domain name
32
+ # @return [Boolean] has information about domain
33
+ def has?(domain_name)
34
+ @domains.key? domain_name
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BBK
4
+ module AMQP
5
+ # Store information about consumed AMQP message
6
+ class Message
7
+
8
+ attr_reader :consumer, :headers, :body, :payload, :delivery_info, :properties
9
+
10
+ def initialize(consumer, delivery_info, properties, body)
11
+ @consumer = consumer
12
+ @properties = properties.to_h.with_indifferent_access
13
+ @body = body
14
+ amqp_consumer = delivery_info[:consumer]
15
+ @delivery_info = delivery_info.to_h.merge(
16
+ message_consumer: consumer,
17
+ protocols: consumer.protocols,
18
+ queue: amqp_consumer&.queue_name
19
+ )
20
+ @headers = @properties.except(:headers).merge(properties[:headers]).with_indifferent_access
21
+ @payload = begin
22
+ Oj.load(body).with_indifferent_access
23
+ rescue StandardError
24
+ {}.with_indifferent_access
25
+ end
26
+ end
27
+
28
+ def message_id
29
+ headers[:message_id]
30
+ end
31
+
32
+ def reply_to
33
+ headers[:reply_to]
34
+ end
35
+
36
+ def ack(*args, answer: nil, **kwargs)
37
+ consumer.ack(self, *args, answer: answer, **kwargs)
38
+ end
39
+
40
+ def nack(*args, error: nil, **kwargs)
41
+ consumer.ack(self, *args, error: error, **kwargs)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'concurrent'
5
+
6
+ module BBK
7
+ module AMQP
8
+ # Publisher send amqp messages
9
+ class Publisher
10
+
11
+ HEADER_PROP_FIELDS = %i[user_id message_id reply_to correlation_id].freeze
12
+ PROTOCOLS = %w[mq amqp amqps].freeze
13
+
14
+ attr_reader :connection, :domains, :logger, :channel, :ack_map, :sended_messages, :channel
15
+
16
+ def initialize(connection, domains, logger: BBK::AMQP.logger)
17
+ @connection = connection
18
+ @channel = connection.channel
19
+ @domains = domains
20
+
21
+ logger = logger.respond_to?(:tagged) ? logger : ActiveSupport::TaggedLogging.new(logger)
22
+ @logger = BBK::Utils::ProxyLogger.new(logger, tags: [self.class.to_s, "Ch##{@channel.id}"])
23
+
24
+ @ack_map = Concurrent::Map.new
25
+ @sended_messages = Concurrent::Map.new
26
+ @configured_exchanges = Set.new
27
+ initialize_callbacks
28
+ end
29
+
30
+ # Returned supported protocols list
31
+ # @return [Array<Symbol>]
32
+ def protocols
33
+ PROTOCOLS
34
+ end
35
+
36
+ # Close publisher - try close amqp channel
37
+ def close
38
+ @channel.tap do |c|
39
+ return nil unless c
40
+
41
+ @channel = nil
42
+ c.close
43
+ end
44
+ end
45
+
46
+ # Publish dispatcher result
47
+ # @param result [BBK::App::Dispatcher::Result] sended result
48
+ def publish(result)
49
+ logger.debug "Try publish dispatcher result #{result.inspect}"
50
+ route = result.route
51
+ result_domain = route.domain
52
+ raise "Unsupported protocol #{route.scheme}" unless PROTOCOLS.include?(route.scheme)
53
+ raise "Unknown domain #{result_domain}" unless domains.has?(result_domain)
54
+
55
+ domain = domains[result_domain]
56
+ raise ArgumentError.new("Unknown route domain #{resutl_domain}") if domain.nil?
57
+
58
+ route_info = domain.call(route)
59
+ message = result.message
60
+ publish_message(route_info.routing_key, message, exchange: route_info.exchange)
61
+ end
62
+
63
+ # Publish message
64
+ # @param routing_key [String] message routing key
65
+ # @param message [Object] (object with headers and payload method)
66
+ # @param exchange [Object] exchange for sending message
67
+ # @param options [Hash] message properties
68
+ def publish_message(routing_key, message, exchange:, options: {})
69
+ logger.debug "Try publish message #{message.headers.inspect}"
70
+ properties = {
71
+ persistent: true,
72
+ mandatory: true,
73
+ routing_key: routing_key,
74
+ headers: message.headers,
75
+ user_id: client_name, # если есть в headers, то на следующей строке будет перетерто
76
+ **message.headers.select {|k| HEADER_PROP_FIELDS.include? k }.compact
77
+ }.merge(options).symbolize_keys
78
+ send_message(exchange, routing_key, message.payload, properties)
79
+ end
80
+
81
+ # Publish raw payload
82
+ # @param routing_key [String] routing key for sending data
83
+ # @param exchange [String] exchange name
84
+ # @param properties [Hash] amqp message properties
85
+ # @param headers [Messag]
86
+ def raw_publish(routing_key, exchange:, properties: {}, headers: {}, payload: {})
87
+ logger.debug "Publish raw message #{headers.inspect}"
88
+ properties = properties.deep_dup
89
+ properties[:headers] = properties.fetch(:headers, {}).merge headers
90
+ properties = properties.merge(headers.select do |k|
91
+ HEADER_PROP_FIELDS.include? k
92
+ end.compact).symbolize_keys
93
+ send_message(exchange, routing_key, payload, properties)
94
+ end
95
+
96
+ private
97
+
98
+ # Initialize amqp callbacks
99
+ def initialize_callbacks
100
+ @channel.confirm_select method(:on_confirm).curry(4).call(channel)
101
+ end
102
+
103
+ # Configure on return callback for exchange
104
+ def configure_exchange(exchange_name)
105
+ return if @configured_exchanges.include?(exchange_name)
106
+
107
+ logger.debug "Configure on_return callback for exchange #{exchange_name}"
108
+ exchange = channel.exchange(exchange_name, passive: true)
109
+ exchange.on_return(&method(:on_return).curry(4).call(exchange))
110
+ @configured_exchanges << exchange_name
111
+ end
112
+
113
+ # Get connection user. If tls connectoin try extract CN from connection tls_cert
114
+ # @return [String] user name
115
+ def client_name
116
+ return connection.user unless connection.ssl?
117
+
118
+ @client_name ||= Utils.commonname(connection.transport.tls_certificate_path)
119
+ end
120
+
121
+ def on_return(exchange, basic_return, properties, body)
122
+ args = { exchange: exchange, basic_return: basic_return, properties: properties,
123
+ body: body }
124
+ message_id = properties[:message_id]
125
+ logger.info "Message with message_id #{message_id} returned #{basic_return.inspect}"
126
+ ack_id, = ack_map.each_pair.find {|_, msg_id| msg_id == message_id }
127
+
128
+ sended_messages.delete(ack_id)&.reject(args) if ack_map.delete(ack_id)
129
+ rescue StandardError => e
130
+ # TODO: возможно стоит попробовать почистить ack_map и sended_messages
131
+ logger.error "[CRITICAL]: #{e.inspect}.\n#{e.backtrace.first(10).join("\n")}"
132
+ end
133
+
134
+ def on_confirm(channel, ack_id, flag, neg)
135
+ logger.debug "Call confirmed callback for message with ack_id #{ack_id} with neg=#{neg}"
136
+ args = { channel: channel, ack_id: ack_id, flag: flag, neg: neg }
137
+ if ack_map.delete(ack_id) && (f = sended_messages.delete(ack_id)).present?
138
+ if neg
139
+ f.reject(args)
140
+ else
141
+ f.fulfill(args)
142
+ end
143
+ end
144
+ rescue StandardError => e
145
+ # TODO: возможно стоит попробовать почистить ack_map и sended_messages
146
+ logger.error "[CRITICAL]: #{e.inspect}.\n#{e.backtrace.first(10).join("\n")}"
147
+ end
148
+
149
+ # Send amqp message and save meta information for processing
150
+ # @param exchange [String] exchange name
151
+ # @param routing_key [String] message routing key
152
+ # @param payload [Object] message payload. Converted to json calling to_json method
153
+ # @param options [Hash] amqp message properties
154
+ # @return [Concurrent::Promises::ResolvableFuture] future for checking success publishing
155
+ def send_message(exchange, routing_key, payload, options)
156
+ configure_exchange(exchange)
157
+ channel.synchronize do
158
+ ack_id = channel.next_publish_seq_no
159
+ options[:message_id] ||= SecureRandom.uuid
160
+ # в случае укаказанного message_id в качестве числа, on_return вернет message_id в качестве строки
161
+ ack_map[ack_id] = options[:message_id].to_s
162
+ future = sended_messages[ack_id] = Concurrent::Promises.resolvable_future
163
+ logger.debug "Publish message #{options[:message_id]} with ack: #{ack_id} to #{exchange}##{routing_key}"
164
+ data = if payload.is_a?(String)
165
+ payload
166
+ else
167
+ Oj.generate(payload)
168
+ end
169
+ channel.basic_publish(data, exchange, routing_key, options)
170
+ future
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+ end
177
+
@@ -0,0 +1,15 @@
1
+ module BBK
2
+ module AMQP
3
+ class RouteInfo
4
+
5
+ attr_reader :exchange, :routing_key
6
+
7
+ def initialize(exchange, routing_key)
8
+ @exchange = exchange
9
+ @routing_key = routing_key
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,68 @@
1
+ require 'bunny'
2
+ require 'fileutils'
3
+ require 'bbk/amqp/utils'
4
+
5
+ module BBK
6
+ module AMQP
7
+ module Spec
8
+ module RabbitHelper
9
+
10
+ cattr_accessor :mq_defaults
11
+
12
+ def self.prepare_certs(fixtures_path)
13
+ FileUtils.mkdir_p File.join(fixtures_path, 'keys')
14
+
15
+ cert_path = File.join(fixtures_path, 'keys', 'cert.pem')
16
+ key_path = File.join(fixtures_path, 'keys', 'key.pem')
17
+ cacert_path = File.join(fixtures_path, 'keys', 'cacert.pem')
18
+
19
+ {
20
+ 'TEST_CLIENT_CERT' => cert_path,
21
+ 'TEST_CLIENT_KEY' => key_path,
22
+ 'CA_CERT' => cacert_path
23
+ }.each_pair do |env, file|
24
+ File.write(file, ENV[env]) if ENV[env].present?
25
+ end
26
+
27
+ [cert_path, key_path, cacert_path]
28
+ end
29
+
30
+ def self.included(_mod)
31
+ cert_path, key_path, cacert_path = prepare_certs(File.join($root, 'fixtures'))
32
+
33
+ self.mq_defaults = {
34
+ host: ENV['MQ_HOST'] || 'mq',
35
+ port: ENV['MQ_PORT'] || 5671,
36
+ tls: true,
37
+ tls_cert: cert_path,
38
+ tls_key: key_path,
39
+ tls_ca_certificates: [cacert_path],
40
+ verify_peer: false,
41
+ verify_ssl: false,
42
+ verify: false,
43
+ auth_mechanism: 'EXTERNAL',
44
+ automatically_recover: false,
45
+ automatic_recovery: false,
46
+ recover_from_connection_close: false,
47
+ continuation_timeout: 10_000,
48
+ heartbeat: 10
49
+ }
50
+ end
51
+
52
+ def commonname
53
+ BBK::AMQP::Utils.commonname(mq_defaults[:tls_cert])
54
+ end
55
+
56
+ def mq_connection(options = {})
57
+ Bunny.new(mq_defaults.merge(options))
58
+ end
59
+
60
+ def mq_pop(queue, timeout = 10)
61
+ BBK::AMQP::Utils.pop queue, timeout
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,2 @@
1
+ require 'bbk/amqp/spec/rabbit_helper'
2
+
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bunny'
4
+ require 'openssl'
5
+
6
+ module BBK
7
+ module AMQP
8
+ module Utils
9
+
10
+ # Try get message from amqp queue
11
+ # @param queue [Bunny::Queue]
12
+ # @param timeout [Integer] in seconds for waiting message message in queue
13
+ # @raise [Timeout::Error] if queue empty in timeout time duration
14
+ # @return [Array] array with delivery_info, metadata and payload
15
+ def self.pop(queue, timeout = 10)
16
+ unblocker = Queue.new
17
+ consumer = queue.subscribe(block: false) do |delivery_info, metadata, payload|
18
+ message = [
19
+ delivery_info,
20
+ metadata.to_hash.with_indifferent_access,
21
+ begin
22
+ Oj.load(payload).with_indifferent_access
23
+ rescue StandardError
24
+ payload
25
+ end
26
+ ]
27
+ unblocker << message
28
+ end
29
+ Thread.new do
30
+ sleep timeout
31
+ unblocker << :timeout
32
+ end
33
+ result = unblocker.pop
34
+ consumer.cancel
35
+ raise ::Timeout::Error if result == :timeout
36
+
37
+ result
38
+ end
39
+
40
+ # Extract CN certificate attribute from certificate path
41
+ # @param cert_path [String] path to certificate file
42
+ # @return [String] certificate CN attribute value
43
+ def self.commonname(cert_path)
44
+ cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
45
+ cert.subject.to_a.find {|name, _, _| name == 'CN' }[1]
46
+ end
47
+
48
+ # Set default options and create non started connection to amqp
49
+ # @option options [String] :hosts List of Amqp hosts (default MQ_HOST env variable or mq)
50
+ # @option options [String] :hostname Amqp host (default MQ_HOST env variable or mq)
51
+ # @option options [Integer] :port Amqp port (default MQ_PORT env variable or 5671 - default tls port)
52
+ # @option options [String] :vhost Connected amqp virtual host (default MQ_VHOST env variable or /)
53
+ # @option options [Boolean] :tls Use tls (default true)
54
+ # @option options [String] :tls_cert Path to certificate file (default config/keys/cert.pem)
55
+ # @option options [String] :tls_key Path to key file (default config/keys/key.pem)
56
+ # @option options [Array] :tls_ca_certificates List to ca certificates (default config/keys/cacert.pem)
57
+ # @option options [String] :verify Verification option server certificate *
58
+ # @option options [String] :verify_peer Verification option server certificate *
59
+ # @option options [String] :verify_ssl Verification option server certificate *
60
+ # @option options [String] :auth_mechanism Amqp authorization mechanism (default EXTERNAL)
61
+ # @option options [Boolean] :automatically_recover Allow automatic network failure recovery (default false)
62
+ # @option options [Boolean] :automatic_recovery Alias for automatically_recover (default false)
63
+ # @option options [Integer] :recovery_attempts Limits the number of connection recovery attempts performed by Bunny (default 0, nil - unlimited)
64
+ # @option options [Boolean] :recover_from_connection_close (default false)
65
+ # @return [Bunny::Session] non started amqp connection
66
+ def self.create_connection(options = {})
67
+ hosts = [options[:hosts] || options[:host] || options[:hostname]].flatten.select(&:present?).uniq
68
+ hosts = hosts.map{|h| h.split(/[;|]/) }.flatten.select(&:present?).uniq
69
+
70
+ options[:hosts] = if hosts.empty?
71
+ [ENV.fetch('MQ_HOST', 'mq')].split(/[;|]/).flatten.select(&:present?).uniq
72
+ else
73
+ hosts
74
+ end
75
+
76
+ options[:port] ||= ENV['MQ_PORT'] || 5671
77
+ options[:vhost] ||= ENV['MQ_VHOST'] || '/'
78
+ user = options[:username] || options[:user] || ENV['MQ_USER']
79
+ options[:username] = options[:user] = user
80
+
81
+ # Передаем пустую строку чтобы bunny не использовал пароль по умолчанию guest
82
+ pwd = options[:password] || options[:pass] || options[:pwd] || ENV['MQ_PASS'] || ''
83
+ options[:password] = options[:pass] = options[:pwd] = pwd
84
+
85
+ options[:tls] = options.fetch(:tls, true)
86
+ options[:tls_cert] ||= 'config/keys/cert.pem'
87
+ options[:tls_key] ||= 'config/keys/key.pem'
88
+ options[:tls_ca_certificates] ||= ['config/keys/cacert.pem']
89
+
90
+ options[:verify] =
91
+ options.fetch(:verify, options.fetch(:verify_peer, options.fetch(:verify_ssl, nil)))
92
+ options[:verify] = true if options[:verify]
93
+ options[:verify_peer] = options[:verify]
94
+ options[:verify_ssl] = options[:verify]
95
+
96
+ options[:auth_mechanism] ||= if options[:tls]
97
+ 'EXTERNAL'
98
+ else
99
+ 'PLAIN'
100
+ end
101
+
102
+ options[:automatically_recover] ||= false
103
+ options[:automatic_recovery] ||= false
104
+ options[:recovery_attempts] ||= 0
105
+ options[:recover_attempts] = options[:recovery_attempts]
106
+ options[:recover_from_connection_close] ||= false
107
+ Bunny.new(options)
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BBK
4
+ module AMQP
5
+
6
+ VERSION = '1.0.0'
7
+
8
+ end
9
+ end
10
+
data/lib/bbk/amqp.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'oj'
6
+ require 'bbk/utils'
7
+ require 'bbk/amqp/version'
8
+ require 'bbk/amqp/utils'
9
+ require 'bbk/amqp/message'
10
+ require 'bbk/amqp/publisher'
11
+ require 'bbk/amqp/consumer'
12
+ require 'bbk/amqp/route_info'
13
+ require 'bbk/amqp/domains_set'
14
+ require 'bbk/amqp/domains/exchange'
15
+ require 'bbk/amqp/domains/by_block'
16
+ require_relative 'bunny_patch'
17
+
18
+
19
+ module BBK
20
+ module AMQP
21
+
22
+ class << self
23
+
24
+ attr_accessor :logger
25
+
26
+ end
27
+
28
+ self.logger = ::BBK::Utils::Logger.default
29
+
30
+ end
31
+ end
32
+
@@ -0,0 +1,10 @@
1
+ module Bunny
2
+ class Transport
3
+
4
+ def host
5
+ @opts[:server_name] || @host
6
+ end
7
+
8
+ end
9
+ end
10
+
@@ -0,0 +1,26 @@
1
+ module BBK
2
+ module AMQP
3
+ class Consumer
4
+ attr_reader connection: untyped
5
+ attr_reader queue_name: String
6
+ attr_reader queue: untyped
7
+ attr_reader options: Hash[String|Symbol, untyped]
8
+ attr_reader logger: Logger|BBK::Utils::_ProxyObject
9
+
10
+ def initialize: (untyped, ?queue_name: String?, **untyped) -> void
11
+
12
+ def protocols: () -> Array[String]
13
+
14
+ def run: (BBK::App::Dispatcher::MessageStream) -> void
15
+
16
+ def ack: (BBK::App::Dispatcher::_IncomingMessage, *untyped, ?answer: BBK::App::Dispatcher::_Message, **untyped) -> void
17
+
18
+ def nack: (BBK::App::Dispatcher::_IncomingMessage, *untyped, ?error: untyped, ?requeue: untyped, **untyped) -> void
19
+
20
+ def stop: () -> void
21
+
22
+ def close: () -> void
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module BBK
2
+ module AMQP
3
+ module Domains
4
+ class ByBlock
5
+ include _Domain
6
+
7
+ def initialize: (String name) {(BBK::App::Dispatcher::Route) -> RouteInfo} -> void
8
+
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module BBK
2
+ module AMQP
3
+ module Domains
4
+ interface _Domain
5
+ def name: () -> String
6
+ def call: (BBK::App::Dispatcher::Route) -> RouteInfo
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module BBK
2
+ module AMQP
3
+ module Domains
4
+ class Exchange
5
+ include _Domain
6
+
7
+ attr_reader exchange: String
8
+
9
+ def initialize: (String, String) -> void
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module BBK
2
+ module AMQP
3
+ class DomainsSet
4
+ def initialize: (*BBK::AMQP::Domains::_Domain) -> void
5
+
6
+ def []: (String) -> BBK::AMQP::Domains::_Domain?
7
+ def add: (BBK::AMQP::Domains::_Domain) -> void
8
+ def each: () ?{(BBK::AMQP::Domains::_Domain) -> void} -> void
9
+ | -> Enumerator[BBK::AMQP::Domains::_Domain, void]
10
+ def has?: (String) -> bool
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module BBK
2
+ module AMQP
3
+ class Message
4
+ include BBK::App::Dispatcher::_IncomingMessage
5
+ # def ack: (*untyped, ?answer: BBK::App::Dispatcher::Result, **untyped) -> void
6
+ # def nack: (*untyped, ?error: untyped, **untyped) -> void
7
+
8
+ def initialize: (BBK::App::Dispatcher::_Consumer, untyped delivery_info, Hash[String|Symbol, untyped] properties, String? body) -> void
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module BBK
2
+ module AMQP
3
+ class Publisher
4
+
5
+ include BBK::App::Dispatcher::_Publisher
6
+ # def publish: (BBK::App::Dispatcher::Result) -> untyped
7
+
8
+ HEADER_PROP_FIELDS: Array[Symbol]
9
+
10
+ attr_reader connection: untyped
11
+
12
+
13
+ def initialize: (untyped connection, untyped domains, ?logger: _Logger) -> void
14
+ def protocols: ()-> Array[String]
15
+
16
+
17
+ def publish_message: (String routing_key, BBK::App::Dispatcher::_Message message, exchange: String, ?options: Hash[String|Symbol, untyped]) -> untyped
18
+
19
+ def raw_publish: (String? routing_key, exchange: String, ?properties: Hash[String|Symbol, untyped], ?headers: Hash[String|Symbol, untyped], ?payload: Hash[String|Symbol, untyped]|String?) -> untyped
20
+
21
+ private
22
+
23
+ def initialize_callbacks: () -> void
24
+ def configure_exchange: (String? exchange_name) -> void
25
+ def client_name: () -> String
26
+ def on_return: (untyped exchange, untyped basic_return, Hash[String|Symbol, untyped] properties, String? body) -> void
27
+ def on_confirm: (untyped channel, Integer ack_id, untyped flag, bool neg) -> untyped
28
+ def send_message: (String exchange, String? routing_key, Hash[untyped, untyped]?|String? payload, Hash[String|Symbol, untyped] options) -> void
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ module BBK
2
+ module AMQP
3
+ class RouteInfo
4
+ attr_reader exchange: String?
5
+ attr_reader routing_key: String?
6
+
7
+ def initialize: (String? exchange, String? routing_key) -> void
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module BBK
2
+ module AMQP
3
+ module Utils
4
+
5
+ def self.pop: (untyped queue, ?Integer timeout) -> void
6
+ def self.commonname: (String cert_path) -> String
7
+ def self.create_connection: (?Hash[Symbol, untyped] options) -> untyped
8
+
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,251 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bbk-amqp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.72904
5
+ platform: ruby
6
+ authors:
7
+ - Samoylenko Yuri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bbk-utils
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bunny
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.19.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.19.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: oj
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bbk-app
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: bunny-mock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec_junit_formatter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubycritic
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov-console
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: AMQP interaction classes for BBK stack
196
+ email:
197
+ - kinnalru@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - Gemfile
203
+ - Gemfile.lock
204
+ - README.md
205
+ - bin/console
206
+ - bin/setup
207
+ - lib/bbk/amqp.rb
208
+ - lib/bbk/amqp/consumer.rb
209
+ - lib/bbk/amqp/domains/by_block.rb
210
+ - lib/bbk/amqp/domains/exchange.rb
211
+ - lib/bbk/amqp/domains_set.rb
212
+ - lib/bbk/amqp/message.rb
213
+ - lib/bbk/amqp/publisher.rb
214
+ - lib/bbk/amqp/route_info.rb
215
+ - lib/bbk/amqp/spec.rb
216
+ - lib/bbk/amqp/spec/rabbit_helper.rb
217
+ - lib/bbk/amqp/utils.rb
218
+ - lib/bbk/amqp/version.rb
219
+ - lib/bbk/bunny_patch.rb
220
+ - sig/bbk/amqp/consumer.rbs
221
+ - sig/bbk/amqp/domains/by_block.rbs
222
+ - sig/bbk/amqp/domains/common.rbs
223
+ - sig/bbk/amqp/domains/exchange.rbs
224
+ - sig/bbk/amqp/domains_set.rbs
225
+ - sig/bbk/amqp/message.rbs
226
+ - sig/bbk/amqp/publisher.rbs
227
+ - sig/bbk/amqp/route_info.rbs
228
+ - sig/bbk/amqp/utils.rbs
229
+ homepage:
230
+ licenses: []
231
+ metadata: {}
232
+ post_install_message:
233
+ rdoc_options: []
234
+ require_paths:
235
+ - lib
236
+ required_ruby_version: !ruby/object:Gem::Requirement
237
+ requirements:
238
+ - - ">="
239
+ - !ruby/object:Gem::Version
240
+ version: '0'
241
+ required_rubygems_version: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ requirements: []
247
+ rubygems_version: 3.2.32
248
+ signing_key:
249
+ specification_version: 4
250
+ summary: AMQP interaction classes for BBK stack
251
+ test_files: []