amqp-client 1.0.0 → 1.1.1

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: 5e6dd3fef130c286e97eadd9bb6d3d4de0fa67599361604cec3560879b9b4bdf
4
- data.tar.gz: c2ce00f897c68d3085427854b08cc98d3487b40bb5327ce38bb216c98357e366
3
+ metadata.gz: 0e248898d176ad9c3d785226ed37c0335052ef701ee16a16bff483089be65dd8
4
+ data.tar.gz: 89b58399edb11b30b05db86e9e4b0a617438e3ed52e63b3e76ae3a4c8cb361fc
5
5
  SHA512:
6
- metadata.gz: 9b62e8ee74ec542be5617b08fbd0e36e5fc16294f99cd0ec61781c7c7425005c426e8358a74abf45a1a24ecb88451b94ef50ee7524403ac7097748b2e94b55b5
7
- data.tar.gz: 5c3e63369578923e20b441fbe9728b374e18150c75ed155291b802c42f80f668b704b39e80b144b90346a8d769d9b71f031b9fca712e6ac52c100a88e46380e3
6
+ metadata.gz: 4500bec819ea9de9546f001eed682ac040ea3c2f93904dabb24afdbbda966d3352d8b1e33687a82c482ce835e7d650172dd906d9ef4ec2efc17f1dea31947314
7
+ data.tar.gz: 62ea49dc2782122146dd2613cd51b1c4132b3eef9d48c5573e9369b0af96042018f104a84cfe2aa2f173cf6d10f5ca3e427baa3ef61faed7029cd7328148a762
@@ -0,0 +1,25 @@
1
+ name: Documentation
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ docs:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - name: Setup Ruby
14
+ uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: 3.0
17
+ - name: Install yard
18
+ run: gem install yard
19
+ - name: Generate docs
20
+ run: yard doc
21
+ - name: Deploy docs
22
+ uses: JamesIves/github-pages-deploy-action@4.1.5
23
+ with:
24
+ branch: gh-pages
25
+ folder: doc
@@ -19,7 +19,7 @@ jobs:
19
19
  strategy:
20
20
  fail-fast: false
21
21
  matrix:
22
- ruby: ['2.7', '3.0']
22
+ ruby: ['2.6', '2.7', '3.0']
23
23
  steps:
24
24
  - uses: actions/checkout@v2
25
25
  - name: Set up Ruby
@@ -32,4 +32,3 @@ jobs:
32
32
  bundle exec rake
33
33
  env:
34
34
  AMQP_PORT: ${{ job.services.rabbitmq.ports[5672] }}
35
-
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.5
4
+ TargetRubyVersion: 2.6
5
5
 
6
6
  Style/StringLiterals:
7
7
  Enabled: true
@@ -12,4 +12,15 @@ Style/StringLiteralsInInterpolation:
12
12
  EnforcedStyle: double_quotes
13
13
 
14
14
  Layout/LineLength:
15
- Max: 120
15
+ Max: 130
16
+
17
+ Naming/FileName:
18
+ Exclude:
19
+ - 'lib/amqp-client.rb'
20
+
21
+ Metrics/PerceivedComplexity:
22
+ Exclude:
23
+ - 'lib/amqp/client/properties.rb'
24
+
25
+ Metrics/ParameterLists:
26
+ Max: 8
data/.rubocop_todo.yml CHANGED
@@ -1,92 +1,48 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2021-06-01 23:54:39 UTC using RuboCop version 1.15.0.
3
+ # on 2021-09-06 19:39:47 UTC using RuboCop version 1.19.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 1
10
- # Cop supports --auto-correct.
11
- # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
12
- # URISchemes: http, https
13
- Layout/LineLength:
14
- Max: 123
15
-
16
- # Offense count: 15
9
+ # Offense count: 18
17
10
  # Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
18
11
  Metrics/AbcSize:
19
- Max: 142
12
+ Max: 179
20
13
 
21
14
  # Offense count: 1
22
15
  # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
23
16
  # IgnoredMethods: refine
24
17
  Metrics/BlockLength:
25
- Max: 37
18
+ Max: 40
26
19
 
27
- # Offense count: 2
20
+ # Offense count: 3
28
21
  # Configuration parameters: CountBlocks.
29
22
  Metrics/BlockNesting:
30
23
  Max: 4
31
24
 
32
- # Offense count: 2
25
+ # Offense count: 5
33
26
  # Configuration parameters: CountComments, CountAsOne.
34
27
  Metrics/ClassLength:
35
- Max: 191
28
+ Max: 400
36
29
 
37
30
  # Offense count: 9
38
31
  # Configuration parameters: IgnoredMethods.
39
32
  Metrics/CyclomaticComplexity:
40
- Max: 30
33
+ Max: 44
41
34
 
42
- # Offense count: 26
35
+ # Offense count: 40
43
36
  # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
44
37
  Metrics/MethodLength:
45
- Max: 124
38
+ Max: 170
46
39
 
47
- # Offense count: 3
40
+ # Offense count: 2
48
41
  # Configuration parameters: CountComments, CountAsOne.
49
42
  Metrics/ModuleLength:
50
- Max: 300
51
-
52
- # Offense count: 5
53
- # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
54
- Metrics/ParameterLists:
55
- Max: 7
43
+ Max: 500
56
44
 
57
- # Offense count: 7
45
+ # Offense count: 4
58
46
  # Configuration parameters: IgnoredMethods.
59
47
  Metrics/PerceivedComplexity:
60
- Max: 18
61
-
62
- # Offense count: 1
63
- # Cop supports --auto-correct.
64
- # Configuration parameters: EnforcedStyle, IgnoredMethods.
65
- # SupportedStyles: predicate, comparison
66
- Style/NumericPredicate:
67
- Exclude:
68
- - 'spec/**/*'
69
- - 'lib/amqp/client/channel.rb'
70
-
71
- # Offense count: 1
72
- # Cop supports --auto-correct.
73
- # Configuration parameters: EnforcedStyle.
74
- # SupportedStyles: implicit, explicit
75
- Style/RescueStandardError:
76
- Exclude:
77
- - 'lib/amqp/client.rb'
78
-
79
- # Offense count: 1
80
- # Cop supports --auto-correct.
81
- # Configuration parameters: EnforcedStyle.
82
- # SupportedStyles: forbid_for_all_comparison_operators, forbid_for_equality_operators_only, require_for_all_comparison_operators, require_for_equality_operators_only
83
- Style/YodaCondition:
84
- Exclude:
85
- - 'lib/amqp/client/channel.rb'
86
-
87
- # Offense count: 1
88
- # Cop supports --auto-correct.
89
- # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
90
- # URISchemes: http, https
91
- Layout/LineLength:
92
- Max: 123
48
+ Max: 22
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-api - LICENSE.txt CHANGELOG.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.1] - 2021-09-15
4
+
5
+ - Added: Examples in the documentation
6
+ - Added: Faster Properties and Table encoding and decoding
7
+
8
+ ## [1.1.0] - 2021-09-08
9
+
10
+ - Fixed: Due to a race condition publishers could get stuck waiting for publish confirms
11
+ - Change: Message, ReturnMessage and Properties are now classes and not structs (for performance reasons)
12
+ - Added: Ruby 2.6 support
13
+ - Added: RBS signatures in sig/amqp-client.rbs
14
+
15
+ ## [1.0.2] - 2021-09-07
16
+
17
+ - Changed: Raise ConnectionClosed and ChannelClosed correctly (previous always ChannelClosed)
18
+ - Fixed: Respect Connection#blocked sent by the broker, will block all writes/requests
19
+
20
+ ## [1.0.1] - 2021-09-06
21
+
22
+ - The API is fully documented! https://cloudamqp.github.io/amqp-client.rb/
23
+ - Fixed: Socket writing is now thread-safe
24
+ - Change: Block while waiting for basic_cancel by default
25
+ - Added: Can specify channel_max, heartbeat and frame_max as options to the Client/Connection
26
+ - Added: Reuse channel 1 to declare high level queues/exchanges
27
+ - Fixed: Only wait for exchange_delete confirmation if not no_wait is set
28
+ - Fixed: Don't raise if Connection#close detects a closed socket (expected)
29
+
3
30
  ## [1.0.0] - 2021-08-27
4
31
 
5
32
  - Verify TLS certificate matches hostname
data/Gemfile CHANGED
@@ -10,3 +10,7 @@ gem "rake", "~> 13.0"
10
10
  gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.7"
13
+
14
+ gem "yard", require: false
15
+
16
+ gem "rubocop-minitest", require: false
data/README.md CHANGED
@@ -1,54 +1,67 @@
1
1
  # AMQP::Client
2
2
 
3
- An AMQP 0-9-1 Ruby client, trying to keep things as simple as possible.
3
+ A modern AMQP 0-9-1 Ruby client. Very fast (just as fast as the Java client, and >4x than other Ruby clients), fully thread-safe, blocking operations and straight-forward error handling.
4
4
 
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
5
+ It's small, only ~1800 lines of code, and without any dependencies. Other Ruby clients are about 4 times bigger. But without trading functionallity.
8
6
 
9
- ```ruby
10
- gem 'amqp-client'
11
- ```
7
+ It's safe by default, messages are published as persistent, and is waiting for confirmation from the broker. That can of course be disabled if performance is a priority.
12
8
 
13
- And then execute:
9
+ ## Support
14
10
 
15
- $ bundle install
11
+ The library is fully supported by [CloudAMQP](https://www.cloudamqp.com), the largest RabbitMQ hosting provider in the world. Open [an issue](https://github.com/cloudamqp/amqp-client.rb/issues) or [email our support](mailto:support@cloudamqp.com) if you have problems or questions.
16
12
 
17
- Or install it yourself as:
13
+ ## Documentation
18
14
 
19
- $ gem install amqp-client
15
+ [API reference](https://cloudamqp.github.io/amqp-client.rb/)
20
16
 
21
17
  ## Usage
22
18
 
23
- Low level API
19
+ The client has two APIs.
20
+
21
+ ### Low level API
22
+
23
+ This API matches the AMQP protocol very well, it can do everything the protocol allows, but requires some knowledge about the protocol, and doesn't handle reconnects.
24
24
 
25
25
  ```ruby
26
26
  require "amqp-client"
27
27
 
28
- c = AMQP::Client.new("amqp://guest:guest@localhost")
29
- conn = c.connect
28
+ # Opens and establishes a connection
29
+ conn = AMQP::Client.new("amqp://guest:guest@localhost").connect
30
+
31
+ # Open a channel
30
32
  ch = conn.channel
33
+
34
+ # Create a temporary queue
31
35
  q = ch.queue_declare
32
- ch.basic_publish "Hello World!", "", q[:queue_name]
33
- msg = ch.basic_get q[:queue_name]
36
+
37
+ # Publish a message to said queue
38
+ ch.basic_publish_confirm "Hello World!", "", q.queue_name, persistent: true
39
+
40
+ # Poll the queue for a message
41
+ msg = ch.basic_get(q.queue_name)
42
+
43
+ # Print the message's body to STDOUT
34
44
  puts msg.body
35
45
  ```
36
46
 
37
- High level API, is an easier and safer API, that only deal with durable queues and persisted messages. All methods are blocking in the case of connection loss etc. It's also fully thread-safe. Don't expect it to have extreme throughput, but expect 100% delivery guarantees (messages might be delivered twice, in the unlikely event of connection loss between message publish and message confirmation by the server).
47
+ ### High level API
48
+
49
+ The library provides a high-level API that is a bit easier to get started with, and also handles reconnection automatically.
38
50
 
39
51
  ```ruby
40
- amqp = AMQP::Client.new("amqp://localhost")
41
- amqp.start
52
+ # Start the client, it will connect and once connected it will reconnect if that connection is lost
53
+ # Operation pending when the connection is lost will raise an exception (not timeout)
54
+ amqp = AMQP::Client.new("amqp://localhost").start
42
55
 
43
56
  # Declares a durable queue
44
- q = amqp.queue("myqueue")
57
+ myqueue = amqp.queue("myqueue")
45
58
 
46
59
  # Bind the queue to any exchange, with any binding key
47
- q.bind("amq.topic", "my.events.*")
60
+ myqueue.bind("amq.topic", "my.events.*")
48
61
 
49
- # The message will be reprocessed if the client loses connection to the server
62
+ # The message will be reprocessed if the client loses connection to the broker
50
63
  # between message arrival and when the message was supposed to be ack'ed.
51
- q.subscribe(prefetch: 20) do |msg|
64
+ myqueue.subscribe(prefetch: 20) do |msg|
52
65
  process(JSON.parse(msg.body))
53
66
  msg.ack
54
67
  rescue
@@ -56,13 +69,37 @@ rescue
56
69
  end
57
70
 
58
71
  # Publish directly to the queue
59
- q.publish { foo: "bar" }.to_json, content_type: "application/json"
72
+ myqueue.publish({ foo: "bar" }.to_json, content_type: "application/json")
60
73
 
61
74
  # Publish to any exchange
62
75
  amqp.publish("my message", "amq.topic", "topic.foo", headers: { foo: 'bar' })
63
76
  amqp.publish(Zlib.gzip("an event"), "amq.topic", "my.event", content_encoding: 'gzip')
64
77
  ```
65
78
 
79
+ ## Supported Ruby versions
80
+
81
+ All maintained Ruby versions are supported.
82
+
83
+ - 3.0
84
+ - 2.7
85
+ - 2.6
86
+
87
+ ## Installation
88
+
89
+ Add this line to your application's Gemfile:
90
+
91
+ ```ruby
92
+ gem 'amqp-client'
93
+ ```
94
+
95
+ And then execute:
96
+
97
+ $ bundle install
98
+
99
+ Or install it yourself as:
100
+
101
+ $ gem install amqp-client
102
+
66
103
  ## Development
67
104
 
68
105
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -71,7 +108,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
71
108
 
72
109
  ## Contributing
73
110
 
74
- Bug reports and pull requests are welcome on GitHub at https://github.com/cloudamqp/amqp-client.rb
111
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/cloudamqp/amqp-client.rb](https://github.com/cloudamqp/amqp-client.rb/)
75
112
 
76
113
  ## License
77
114
 
data/Rakefile CHANGED
@@ -11,6 +11,8 @@ end
11
11
 
12
12
  require "rubocop/rake_task"
13
13
 
14
- RuboCop::RakeTask.new
14
+ RuboCop::RakeTask.new do |task|
15
+ task.requires << "rubocop-minitest"
16
+ end
15
17
 
16
18
  task default: %i[test rubocop]
data/amqp-client.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Work in progress"
13
13
  spec.homepage = "https://github.com/cloudamqp/amqp-client.rb"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
@@ -3,338 +3,550 @@
3
3
  require_relative "./message"
4
4
 
5
5
  module AMQP
6
- # AMQP Channel
7
- class Channel
8
- def initialize(connection, id)
9
- @connection = connection
10
- @id = id
11
- @replies = ::Queue.new
12
- @consumers = {}
13
- @closed = nil
14
- @open = false
15
- @on_return = nil
16
- @confirm = nil
17
- @unconfirmed = ::Queue.new
18
- @unconfirmed_empty = ::Queue.new
19
- @basic_gets = ::Queue.new
20
- end
21
-
22
- def inspect
23
- "#<#{self.class} @id=#{@id} @open=#{@open} @closed=#{@closed} confirm_selected=#{!@confirm.nil?}"\
24
- " consumer_count=#{@consumers.size} replies_count=#{@replies.size} unconfirmed_count=#{@unconfirmed.size}>"
25
- end
26
-
27
- attr_reader :id
28
-
29
- def open
30
- return self if @open
6
+ class Client
7
+ class Connection
8
+ # AMQP Channel
9
+ class Channel
10
+ # Should only be called from Connection
11
+ # @param connection [Connection] The connection this channel belongs to
12
+ # @param id [Integer] ID of the channel
13
+ # @see Connection#channel
14
+ # @api private
15
+ def initialize(connection, id)
16
+ @connection = connection
17
+ @id = id
18
+ @replies = ::Queue.new
19
+ @consumers = {}
20
+ @closed = nil
21
+ @open = false
22
+ @on_return = nil
23
+ @confirm = nil
24
+ @unconfirmed = ::Queue.new
25
+ @unconfirmed_empty = ::Queue.new
26
+ @basic_gets = ::Queue.new
27
+ end
31
28
 
32
- @open = true
33
- write_bytes FrameBytes.channel_open(@id)
34
- expect(:channel_open_ok)
35
- self
36
- end
29
+ # Override #inspect
30
+ # @api private
31
+ def inspect
32
+ "#<#{self.class} @id=#{@id} @open=#{@open} @closed=#{@closed} confirm_selected=#{!@confirm.nil?}"\
33
+ " consumer_count=#{@consumers.size} replies_count=#{@replies.size} unconfirmed_count=#{@unconfirmed.size}>"
34
+ end
37
35
 
38
- def close(reason = "", code = 200)
39
- return if @closed
36
+ # Channel ID
37
+ # @return [Integer]
38
+ attr_reader :id
40
39
 
41
- write_bytes FrameBytes.channel_close(@id, reason, code)
42
- @closed = [code, reason]
43
- expect :channel_close_ok
44
- @replies.close
45
- @basic_gets.close
46
- @unconfirmed_empty.close
47
- @consumers.each_value(&:close)
48
- end
40
+ # Open the channel (called from Connection)
41
+ # @return [Channel] self
42
+ # @api private
43
+ def open
44
+ return self if @open
49
45
 
50
- # Called when channel is closed by server
51
- def closed!(code, reason, classid, methodid)
52
- @closed = [code, reason, classid, methodid]
53
- @replies.close
54
- @basic_gets.close
55
- @unconfirmed_empty.close
56
- @consumers.each_value(&:close)
57
- end
46
+ @open = true
47
+ write_bytes FrameBytes.channel_open(@id)
48
+ expect(:channel_open_ok)
49
+ self
50
+ end
58
51
 
59
- def exchange_declare(name, type, passive: false, durable: true, auto_delete: false, internal: false, arguments: {})
60
- write_bytes FrameBytes.exchange_declare(@id, name, type, passive, durable, auto_delete, internal, arguments)
61
- expect :exchange_declare_ok
62
- end
52
+ # Gracefully close a connection
53
+ # @return [nil]
54
+ def close(reason: "", code: 200)
55
+ return if @closed
56
+
57
+ write_bytes FrameBytes.channel_close(@id, reason, code)
58
+ @closed = [:channel, code, reason]
59
+ expect :channel_close_ok
60
+ @replies.close
61
+ @basic_gets.close
62
+ @unconfirmed_empty.close
63
+ @consumers.each_value(&:close)
64
+ nil
65
+ end
63
66
 
64
- def exchange_delete(name, if_unused: false, no_wait: false)
65
- write_bytes FrameBytes.exchange_delete(@id, name, if_unused, no_wait)
66
- expect :exchange_delete_ok
67
- end
67
+ # Called when channel is closed by broker
68
+ # @param level [Symbol] :connection or :channel
69
+ # @return [nil]
70
+ # @api private
71
+ def closed!(level, code, reason, classid, methodid)
72
+ @closed = [level, code, reason, classid, methodid]
73
+ @replies.close
74
+ @basic_gets.close
75
+ @unconfirmed_empty.close
76
+ @consumers.each_value(&:close)
77
+ nil
78
+ end
68
79
 
69
- def exchange_bind(destination, source, binding_key, arguments: {})
70
- write_bytes FrameBytes.exchange_bind(@id, destination, source, binding_key, false, arguments)
71
- expect :exchange_bind_ok
72
- end
80
+ # Handle returned messages in this block. If not set the message will just be logged to STDERR
81
+ # @yield [ReturnMessage] Messages returned by the broker when a publish has failed
82
+ # @return nil
83
+ def on_return(&block)
84
+ @on_return = block
85
+ nil
86
+ end
73
87
 
74
- def exchange_unbind(destination, source, binding_key, arguments: {})
75
- write_bytes FrameBytes.exchange_unbind(@id, destination, source, binding_key, false, arguments)
76
- expect :exchange_unbind_ok
77
- end
88
+ # @!group Exchange
89
+
90
+ # Declare an exchange
91
+ # @param name [String] Name of the exchange
92
+ # @param type [String] Type of exchange (amq.direct, amq.fanout, amq.topic, amq.headers, etc.)
93
+ # @param passive [Boolean] If true raise an exception if the exchange doesn't already exists
94
+ # @param durable [Boolean] If true the exchange will persist between broker restarts,
95
+ # also a requirement for persistent messages
96
+ # @param auto_delete [Boolean] If true the exchange will be deleted when the last queue/exchange is unbound
97
+ # @param internal [Boolean] If true the exchange can't be published to directly
98
+ # @param arguments [Hash] Custom arguments
99
+ # @return [nil]
100
+ def exchange_declare(name, type, passive: false, durable: true, auto_delete: false, internal: false, arguments: {})
101
+ write_bytes FrameBytes.exchange_declare(@id, name, type, passive, durable, auto_delete, internal, arguments)
102
+ expect :exchange_declare_ok
103
+ nil
104
+ end
78
105
 
79
- QueueOk = Struct.new(:queue_name, :message_count, :consumer_count)
106
+ # Delete an exchange
107
+ # @param name [String] Name of the exchange
108
+ # @param if_unused [Boolean] If true raise an exception if queues/exchanges is bound to this exchange
109
+ # @param no_wait [Boolean] If true don't wait for a broker confirmation
110
+ # @return [nil]
111
+ def exchange_delete(name, if_unused: false, no_wait: false)
112
+ write_bytes FrameBytes.exchange_delete(@id, name, if_unused, no_wait)
113
+ expect :exchange_delete_ok unless no_wait
114
+ nil
115
+ end
80
116
 
81
- def queue_declare(name = "", passive: false, durable: true, exclusive: false, auto_delete: false, arguments: {})
82
- durable = false if name.empty?
83
- exclusive = true if name.empty?
84
- auto_delete = true if name.empty?
117
+ # Bind an exchange to another exchange
118
+ # @param destination [String] Name of the exchange to bind
119
+ # @param source [String] Name of the exchange to bind to
120
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
121
+ # @param arguments [Hash] Message headers to match on, but only when bound to header exchanges
122
+ # @return [nil]
123
+ def exchange_bind(destination, source, binding_key, arguments: {})
124
+ write_bytes FrameBytes.exchange_bind(@id, destination, source, binding_key, false, arguments)
125
+ expect :exchange_bind_ok
126
+ nil
127
+ end
85
128
 
86
- write_bytes FrameBytes.queue_declare(@id, name, passive, durable, exclusive, auto_delete, arguments)
87
- name, message_count, consumer_count = expect(:queue_declare_ok)
129
+ # Unbind an exchange from another exchange
130
+ # @param destination [String] Name of the exchange to unbind
131
+ # @param source [String] Name of the exchange to unbind from
132
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
133
+ # @param arguments [Hash] Arguments matching the binding that's being removed
134
+ # @return [nil]
135
+ def exchange_unbind(destination, source, binding_key, arguments: {})
136
+ write_bytes FrameBytes.exchange_unbind(@id, destination, source, binding_key, false, arguments)
137
+ expect :exchange_unbind_ok
138
+ nil
139
+ end
88
140
 
89
- QueueOk.new(name, message_count, consumer_count)
90
- end
141
+ # @!endgroup
142
+ # @!group Queue
143
+
144
+ # Response when declaring a Queue
145
+ # @!attribute queue_name
146
+ # @return [String] The name of the queue
147
+ # @!attribute message_count
148
+ # @return [Integer] Number of messages in the queue at the time of declaration
149
+ # @!attribute consumer_count
150
+ # @return [Integer] Number of consumers subscribed to the queue at the time of declaration
151
+ QueueOk = Struct.new(:queue_name, :message_count, :consumer_count)
152
+
153
+ # Create a queue (operation is idempotent)
154
+ # @param name [String] Name of the queue, can be empty, but will then be generated by the broker
155
+ # @param passive [Boolean] If true an exception will be raised if the queue doesn't already exists
156
+ # @param durable [Boolean] If true the queue will survive broker restarts,
157
+ # messages in the queue will only survive if they are published as persistent
158
+ # @param exclusive [Boolean] If true the queue will be deleted when the channel is closed
159
+ # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
160
+ # (it won't be deleted until at least one consumer has consumed from it)
161
+ # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
162
+ # @return [QueueOk] The QueueOk struct got `queue_name`, `message_count` and `consumer_count` properties
163
+ def queue_declare(name = "", passive: false, durable: true, exclusive: false, auto_delete: false, arguments: {})
164
+ durable = false if name.empty?
165
+ exclusive = true if name.empty?
166
+ auto_delete = true if name.empty?
167
+
168
+ write_bytes FrameBytes.queue_declare(@id, name, passive, durable, exclusive, auto_delete, arguments)
169
+ name, message_count, consumer_count = expect(:queue_declare_ok)
170
+
171
+ QueueOk.new(name, message_count, consumer_count)
172
+ end
91
173
 
92
- def queue_delete(name, if_unused: false, if_empty: false, no_wait: false)
93
- write_bytes FrameBytes.queue_delete(@id, name, if_unused, if_empty, no_wait)
94
- message_count, = expect :queue_delete
95
- message_count
96
- end
174
+ # Delete a queue
175
+ # @param name [String] Name of the queue
176
+ # @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
177
+ # @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
178
+ # @param no_wait [Boolean] Don't wait for a broker confirmation if true
179
+ # @return [Integer] Number of messages in queue when deleted
180
+ # @return [nil] If no_wait was set true
181
+ def queue_delete(name, if_unused: false, if_empty: false, no_wait: false)
182
+ write_bytes FrameBytes.queue_delete(@id, name, if_unused, if_empty, no_wait)
183
+ message_count, = expect :queue_delete unless no_wait
184
+ message_count
185
+ end
97
186
 
98
- def queue_bind(name, exchange, binding_key, arguments: {})
99
- write_bytes FrameBytes.queue_bind(@id, name, exchange, binding_key, false, arguments)
100
- expect :queue_bind_ok
101
- end
187
+ # Bind a queue to an exchange
188
+ # @param name [String] Name of the queue to bind
189
+ # @param exchange [String] Name of the exchange to bind to
190
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
191
+ # @param arguments [Hash] Message headers to match on, but only when bound to header exchanges
192
+ # @return [nil]
193
+ def queue_bind(name, exchange, binding_key, arguments: {})
194
+ write_bytes FrameBytes.queue_bind(@id, name, exchange, binding_key, false, arguments)
195
+ expect :queue_bind_ok
196
+ nil
197
+ end
102
198
 
103
- def queue_purge(name, no_wait: false)
104
- write_bytes FrameBytes.queue_purge(@id, name, no_wait)
105
- expect :queue_purge_ok unless no_wait
106
- end
199
+ # Purge a queue
200
+ # @param name [String] Name of the queue
201
+ # @param no_wait [Boolean] Don't wait for a broker confirmation if true
202
+ # @return [nil]
203
+ def queue_purge(name, no_wait: false)
204
+ write_bytes FrameBytes.queue_purge(@id, name, no_wait)
205
+ expect :queue_purge_ok unless no_wait
206
+ nil
207
+ end
107
208
 
108
- def queue_unbind(name, exchange, binding_key, arguments: {})
109
- write_bytes FrameBytes.queue_unbind(@id, name, exchange, binding_key, arguments)
110
- expect :queue_unbind_ok
111
- end
209
+ # Unbind a queue from an exchange
210
+ # @param name [String] Name of the queue to unbind
211
+ # @param exchange [String] Name of the exchange to unbind from
212
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
213
+ # @param arguments [Hash] Arguments matching the binding that's being removed
214
+ # @return [nil]
215
+ def queue_unbind(name, exchange, binding_key, arguments: {})
216
+ write_bytes FrameBytes.queue_unbind(@id, name, exchange, binding_key, arguments)
217
+ expect :queue_unbind_ok
218
+ nil
219
+ end
112
220
 
113
- def basic_get(queue_name, no_ack: true)
114
- write_bytes FrameBytes.basic_get(@id, queue_name, no_ack)
115
- case (msg = @basic_gets.pop)
116
- when Message then msg
117
- when :basic_get_empty then nil
118
- when nil then raise AMQP::Client::ChannelClosedError.new(@id, *@closed)
119
- end
120
- end
221
+ # @!endgroup
222
+ # @!group Basic
223
+
224
+ # Get a message from a queue (by polling)
225
+ # @param queue_name [String]
226
+ # @param no_ack [Boolean] When false the message have to be manually acknowledged
227
+ # @return [Message] If the queue had a message
228
+ # @return [nil] If the queue doesn't have any messages
229
+ def basic_get(queue_name, no_ack: true)
230
+ write_bytes FrameBytes.basic_get(@id, queue_name, no_ack)
231
+ case (msg = @basic_gets.pop)
232
+ when Message then msg
233
+ when :basic_get_empty then nil
234
+ when nil then raise Error::Closed.new(@id, *@closed)
235
+ end
236
+ end
121
237
 
122
- def basic_publish(body, exchange, routing_key, **properties)
123
- frame_max = @connection.frame_max - 8
124
- id = @id
125
- mandatory = properties.delete(:mandatory) || false
126
- case properties.delete(:persistent)
127
- when true then properties[:delivery_mode] = 2
128
- when false then properties[:delivery_mode] = 1
129
- end
238
+ # Publishes a message to an exchange
239
+ # @param body [String] The body, can be a string or a byte array
240
+ # @param exchange [String] Name of the exchange to publish to
241
+ # @param routing_key [String] The routing key that the exchange might use to route the message to a queue
242
+ # @param properties [Properties]
243
+ # @option properties [Boolean] mandatory The message will be returned if the message can't be routed to a queue
244
+ # @option properties [Boolean] persistent Same as delivery_mode: 2
245
+ # @option properties [String] content_type Content type of the message body
246
+ # @option properties [String] content_encoding Content encoding of the body
247
+ # @option properties [Hash<String, Object>] headers Custom headers
248
+ # @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
249
+ # @option properties [Integer] priority A priority of the message (between 0 and 255)
250
+ # @option properties [Integer] correlation_id A correlation id, most often used used for RPC communication
251
+ # @option properties [String] reply_to Queue to reply RPC responses to
252
+ # @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
253
+ # @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
254
+ # @option properties [Date] timestamp Often used for the time the message was originally generated
255
+ # @option properties [String] type Can indicate what kind of message this is
256
+ # @option properties [String] user_id Can be used to verify that this is the user that published the message
257
+ # @option properties [String] app_id Can be used to indicates which app that generated the message
258
+ # @return [nil]
259
+ def basic_publish(body, exchange, routing_key, **properties)
260
+ body_max = @connection.frame_max - 8
261
+ id = @id
262
+ mandatory = properties.delete(:mandatory) || false
263
+ case properties.delete(:persistent)
264
+ when true then properties[:delivery_mode] = 2
265
+ when false then properties[:delivery_mode] = 1
266
+ end
130
267
 
131
- if body.bytesize.between?(1, frame_max)
132
- write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
133
- FrameBytes.header(id, body.bytesize, properties),
134
- FrameBytes.body(id, body)
135
- @unconfirmed.push @confirm += 1 if @confirm
136
- return
137
- end
268
+ if body.bytesize.between?(1, body_max)
269
+ write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
270
+ FrameBytes.header(id, body.bytesize, properties),
271
+ FrameBytes.body(id, body)
272
+ @unconfirmed.push @confirm += 1 if @confirm
273
+ return
274
+ end
138
275
 
139
- write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
140
- FrameBytes.header(id, body.bytesize, properties)
141
- pos = 0
142
- while pos < body.bytesize # split body into multiple frame_max frames
143
- len = [frame_max, body.bytesize - pos].min
144
- body_part = body.byteslice(pos, len)
145
- write_bytes FrameBytes.body(id, body_part)
146
- pos += len
147
- end
148
- @unconfirmed.push @confirm += 1 if @confirm
149
- nil
150
- end
276
+ write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
277
+ FrameBytes.header(id, body.bytesize, properties)
278
+ pos = 0
279
+ while pos < body.bytesize # split body into multiple frame_max frames
280
+ len = [body_max, body.bytesize - pos].min
281
+ body_part = body.byteslice(pos, len)
282
+ write_bytes FrameBytes.body(id, body_part)
283
+ pos += len
284
+ end
285
+ @unconfirmed.push @confirm += 1 if @confirm
286
+ nil
287
+ end
151
288
 
152
- def basic_publish_confirm(body, exchange, routing_key, **properties)
153
- confirm_select(no_wait: true)
154
- basic_publish(body, exchange, routing_key, **properties)
155
- wait_for_confirms
156
- end
289
+ # Publish a message and block until the message has confirmed it has received it
290
+ # @param (see #basic_publish)
291
+ # @option (see #basic_publish)
292
+ # @return [Boolean] True if the message was successfully published
293
+ # @raise (see #basic_publish)
294
+ def basic_publish_confirm(body, exchange, routing_key, **properties)
295
+ confirm_select(no_wait: true)
296
+ basic_publish(body, exchange, routing_key, **properties)
297
+ wait_for_confirms
298
+ end
157
299
 
158
- # Consume from a queue
159
- # worker_threads: 0 => blocking, messages are executed in the thread calling this method
160
- def basic_consume(queue, tag: "", no_ack: true, exclusive: false, arguments: {}, worker_threads: 1)
161
- write_bytes FrameBytes.basic_consume(@id, queue, tag, no_ack, exclusive, arguments)
162
- tag, = expect(:basic_consume_ok)
163
- q = @consumers[tag] = ::Queue.new
164
- if worker_threads.zero?
165
- loop do
166
- yield (q.pop || break)
167
- end
168
- else
169
- threads = Array.new(worker_threads) do
170
- Thread.new do
300
+ # Consume messages from a queue
301
+ # @param queue [String] Name of the queue to subscribe to
302
+ # @param tag [String] Custom consumer tag, will be auto assigned by the broker if empty.
303
+ # Has to be uniqe among this channel's consumers only
304
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
305
+ # @param exclusive [Boolean] When true only a single consumer can consume from the queue at a time
306
+ # @param arguments [Hash] Custom arguments for the consumer
307
+ # @param worker_threads [Integer] Number of threads processing messages,
308
+ # 0 means that the thread calling this method will process the messages and thus this method will block
309
+ # @yield [Message] Delivered message from the queue
310
+ # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
311
+ # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
312
+ def basic_consume(queue, tag: "", no_ack: true, exclusive: false, arguments: {}, worker_threads: 1)
313
+ write_bytes FrameBytes.basic_consume(@id, queue, tag, no_ack, exclusive, arguments)
314
+ tag, = expect(:basic_consume_ok)
315
+ q = @consumers[tag] = ::Queue.new
316
+ if worker_threads.zero?
171
317
  loop do
172
318
  yield (q.pop || break)
173
319
  end
320
+ nil
321
+ else
322
+ threads = Array.new(worker_threads) do
323
+ Thread.new do
324
+ loop do
325
+ yield (q.pop || break)
326
+ end
327
+ end
328
+ end
329
+ [tag, threads]
174
330
  end
175
331
  end
176
- [tag, threads]
177
- end
178
- end
179
332
 
180
- def basic_cancel(consumer_tag, no_wait: false)
181
- consumer = @consumers.fetch(consumer_tag)
182
- return if consumer.closed?
333
+ # Cancel/abort/stop a consumer
334
+ # @param consumer_tag [String] Tag of the consumer to cancel
335
+ # @param no_wait [Boolean] Will wait for a confirmation from the broker that the consumer is cancelled
336
+ # @return [nil]
337
+ def basic_cancel(consumer_tag, no_wait: false)
338
+ consumer = @consumers.fetch(consumer_tag)
339
+ return if consumer.closed?
340
+
341
+ write_bytes FrameBytes.basic_cancel(@id, consumer_tag)
342
+ expect(:basic_cancel_ok) unless no_wait
343
+ consumer.close
344
+ nil
345
+ end
183
346
 
184
- write_bytes FrameBytes.basic_cancel(@id, consumer_tag)
185
- expect(:basic_cancel_ok) unless no_wait
186
- consumer.close
187
- end
347
+ # Specify how many messages to prefetch for consumers with `no_ack: false`
348
+ # @param prefetch_count [Integer] Number of messages to maxium keep in flight
349
+ # @param prefetch_size [Integer] Number of bytes to maxium keep in flight
350
+ # @param global [Boolean] If true the limit will apply to channel rather than the consumer
351
+ # @return [nil]
352
+ def basic_qos(prefetch_count, prefetch_size: 0, global: false)
353
+ write_bytes FrameBytes.basic_qos(@id, prefetch_size, prefetch_count, global)
354
+ expect :basic_qos_ok
355
+ nil
356
+ end
188
357
 
189
- def basic_qos(prefetch_count, prefetch_size: 0, global: false)
190
- write_bytes FrameBytes.basic_qos(@id, prefetch_size, prefetch_count, global)
191
- expect :basic_qos_ok
192
- end
358
+ # Acknowledge a message
359
+ # @param delivery_tag [Integer] The delivery tag of the message to acknowledge
360
+ # @return [nil]
361
+ def basic_ack(delivery_tag, multiple: false)
362
+ write_bytes FrameBytes.basic_ack(@id, delivery_tag, multiple)
363
+ nil
364
+ end
193
365
 
194
- def basic_ack(delivery_tag, multiple: false)
195
- write_bytes FrameBytes.basic_ack(@id, delivery_tag, multiple)
196
- end
366
+ # Negatively acknowledge a message
367
+ # @param delivery_tag [Integer] The delivery tag of the message to acknowledge
368
+ # @param multiple [Boolean] Nack all messages up to this message
369
+ # @param requeue [Boolean] Requeue the message
370
+ # @return [nil]
371
+ def basic_nack(delivery_tag, multiple: false, requeue: false)
372
+ write_bytes FrameBytes.basic_nack(@id, delivery_tag, multiple, requeue)
373
+ nil
374
+ end
197
375
 
198
- def basic_nack(delivery_tag, multiple: false, requeue: false)
199
- write_bytes FrameBytes.basic_nack(@id, delivery_tag, multiple, requeue)
200
- end
376
+ # Reject a message
377
+ # @param delivery_tag [Integer] The delivery tag of the message to acknowledge
378
+ # @param requeue [Boolean] Requeue the message into the queue again
379
+ # @return [nil]
380
+ def basic_reject(delivery_tag, requeue: false)
381
+ write_bytes FrameBytes.basic_reject(@id, delivery_tag, requeue)
382
+ nil
383
+ end
201
384
 
202
- def basic_reject(delivery_tag, requeue: false)
203
- write_bytes FrameBytes.basic_reject(@id, delivery_tag, requeue)
204
- end
385
+ # Recover all the unacknowledge messages
386
+ # @param requeue [Boolean] If false the currently unack:ed messages will be deliviered to this consumer again,
387
+ # if true to any consumer
388
+ # @return [nil]
389
+ def basic_recover(requeue: false)
390
+ write_bytes FrameBytes.basic_recover(@id, requeue: requeue)
391
+ expect :basic_recover_ok
392
+ nil
393
+ end
205
394
 
206
- def basic_recover(requeue: false)
207
- write_bytes FrameBytes.basic_recover(@id, requeue: requeue)
208
- expect :basic_recover_ok
209
- end
395
+ # @!endgroup
396
+ # @!group Confirm
210
397
 
211
- def confirm_select(no_wait: false)
212
- return if @confirm
398
+ # Put the channel in confirm mode, each published message will then be confirmed by the broker
399
+ # @param no_wait [Boolean] If false the method will block until the broker has confirmed the request
400
+ # @return [nil]
401
+ def confirm_select(no_wait: false)
402
+ return if @confirm
213
403
 
214
- write_bytes FrameBytes.confirm_select(@id, no_wait)
215
- expect :confirm_select_ok unless no_wait
216
- @confirm = 0
217
- end
404
+ write_bytes FrameBytes.confirm_select(@id, no_wait)
405
+ expect :confirm_select_ok unless no_wait
406
+ @confirm = 0
407
+ nil
408
+ end
218
409
 
219
- # Block until all publishes messages are confirmed
220
- def wait_for_confirms
221
- return true if @unconfirmed.empty?
410
+ # Block until all publishes messages are confirmed
411
+ # @return [Boolean] True if all message where positivly acknowledged, false if not
412
+ def wait_for_confirms
413
+ return true if @unconfirmed.empty?
222
414
 
223
- case @unconfirmed_empty.pop
224
- when true then true
225
- when false then false
226
- else raise AMQP::Client::ChannelClosedError.new(@id, *@closed)
227
- end
228
- end
415
+ ok = @unconfirmed_empty.pop
416
+ raise Error::Closed.new(@id, *@closed) if ok.nil?
229
417
 
230
- # Called by Connection when received ack/nack from server
231
- def confirm(args)
232
- ack_or_nack, delivery_tag, multiple = *args
233
- loop do
234
- tag = @unconfirmed.pop(true)
235
- break if tag == delivery_tag
236
- next if multiple && tag < delivery_tag
237
-
238
- @unconfirmed << tag # requeue
239
- rescue ThreadError
240
- break
241
- end
242
- return unless @unconfirmed.empty?
418
+ ok
419
+ end
243
420
 
244
- @unconfirmed_empty.num_waiting.times do
245
- @unconfirmed_empty << (ack_or_nack == :ack)
246
- end
247
- end
421
+ # Called by Connection when received ack/nack from broker
422
+ # @api private
423
+ def confirm(args)
424
+ ack_or_nack, delivery_tag, multiple = *args
425
+ loop do
426
+ tag = @unconfirmed.pop(true)
427
+ break if tag == delivery_tag
428
+ next if multiple && tag < delivery_tag
429
+
430
+ @unconfirmed << tag # requeue
431
+ rescue ThreadError
432
+ break
433
+ end
434
+ return unless @unconfirmed.empty?
248
435
 
249
- def tx_select
250
- write_bytes FrameBytes.tx_select(@id)
251
- expect :tx_select_ok
252
- end
436
+ ok = ack_or_nack == :ack
437
+ @unconfirmed_empty.push(ok) until @unconfirmed_empty.num_waiting.zero?
438
+ end
253
439
 
254
- def tx_commit
255
- write_bytes FrameBytes.tx_commit(@id)
256
- expect :tx_commit_ok
257
- end
440
+ # @!endgroup
441
+ # @!group Transaction
258
442
 
259
- def tx_rollback
260
- write_bytes FrameBytes.tx_rollback(@id)
261
- expect :tx_rollback_ok
262
- end
443
+ # Put the channel in transaction mode, make sure that you #tx_commit or #tx_rollback after publish
444
+ # @return [nil]
445
+ def tx_select
446
+ write_bytes FrameBytes.tx_select(@id)
447
+ expect :tx_select_ok
448
+ nil
449
+ end
263
450
 
264
- def on_return(&block)
265
- @on_return = block
266
- end
451
+ # Commmit a transaction, requires that the channel is in transaction mode
452
+ # @return [nil]
453
+ def tx_commit
454
+ write_bytes FrameBytes.tx_commit(@id)
455
+ expect :tx_commit_ok
456
+ nil
457
+ end
267
458
 
268
- def reply(args)
269
- @replies.push(args)
270
- end
459
+ # Rollback a transaction, requires that the channel is in transaction mode
460
+ # @return [nil]
461
+ def tx_rollback
462
+ write_bytes FrameBytes.tx_rollback(@id)
463
+ expect :tx_rollback_ok
464
+ nil
465
+ end
271
466
 
272
- def message_returned(reply_code, reply_text, exchange, routing_key)
273
- @next_msg = ReturnMessage.new(reply_code, reply_text, exchange, routing_key, nil, "")
274
- end
467
+ # @!endgroup
275
468
 
276
- def message_delivered(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
277
- @next_msg = Message.new(self, delivery_tag, exchange, routing_key, nil, "", redelivered, consumer_tag)
278
- end
469
+ # @api private
470
+ def reply(args)
471
+ @replies.push(args)
472
+ end
279
473
 
280
- def basic_get_empty
281
- @basic_gets.push :basic_get_empty
282
- end
474
+ # @api private
475
+ def message_returned(reply_code, reply_text, exchange, routing_key)
476
+ @next_msg = ReturnMessage.new(reply_code, reply_text, exchange, routing_key)
477
+ end
283
478
 
284
- def header_delivered(body_size, properties)
285
- @next_msg.properties = properties
286
- if body_size.zero?
287
- next_message_finished!
288
- else
289
- @next_body = StringIO.new(String.new(capacity: body_size))
290
- @next_body_size = body_size
291
- end
292
- end
479
+ # @api private
480
+ def message_delivered(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
481
+ @next_msg = Message.new(self, consumer_tag, delivery_tag, exchange, routing_key, redelivered)
482
+ end
293
483
 
294
- def body_delivered(body_part)
295
- @next_body.write(body_part)
296
- return unless @next_body.pos == @next_body_size
484
+ # @api private
485
+ def basic_get_empty
486
+ @basic_gets.push :basic_get_empty
487
+ end
297
488
 
298
- @next_msg.body = @next_body.string
299
- next_message_finished!
300
- end
489
+ # @api private
490
+ def header_delivered(body_size, properties)
491
+ @next_msg.properties = properties
492
+ if body_size.zero?
493
+ next_message_finished!
494
+ else
495
+ @next_body = StringIO.new(String.new(capacity: body_size))
496
+ @next_body_size = body_size
497
+ end
498
+ end
301
499
 
302
- def close_consumer(tag)
303
- @consumers.fetch(tag).close
304
- end
500
+ # @api private
501
+ def body_delivered(body_part)
502
+ @next_body.write(body_part)
503
+ return unless @next_body.pos == @next_body_size
305
504
 
306
- private
307
-
308
- def next_message_finished!
309
- next_msg = @next_msg
310
- if next_msg.is_a? ReturnMessage
311
- if @on_return
312
- Thread.new { @on_return.call(next_msg) }
313
- else
314
- warn "AMQP-Client message returned: #{msg.inspect}"
315
- end
316
- elsif next_msg.consumer_tag.nil?
317
- @basic_gets.push next_msg
318
- else
319
- Thread.pass until (consumer = @consumers[next_msg.consumer_tag])
320
- consumer.push next_msg
321
- end
322
- ensure
323
- @next_msg = @next_body = @next_body_size = nil
324
- end
505
+ @next_msg.body = @next_body.string
506
+ next_message_finished!
507
+ end
325
508
 
326
- def write_bytes(*bytes)
327
- raise AMQP::Client::ChannelClosedError.new(@id, *@closed) if @closed
509
+ # @api private
510
+ def close_consumer(tag)
511
+ @consumers.fetch(tag).close
512
+ nil
513
+ end
328
514
 
329
- @connection.write_bytes(*bytes)
330
- end
515
+ private
331
516
 
332
- def expect(expected_frame_type)
333
- frame_type, *args = @replies.pop
334
- raise AMQP::Client::ChannelClosedError.new(@id, *@closed) if frame_type.nil?
335
- raise AMQP::Client::UnexpectedFrame.new(expected_frame_type, frame_type) unless frame_type == expected_frame_type
517
+ def next_message_finished!
518
+ next_msg = @next_msg
519
+ if next_msg.is_a? ReturnMessage
520
+ if @on_return
521
+ Thread.new { @on_return.call(next_msg) }
522
+ else
523
+ warn "AMQP-Client message returned: #{msg.inspect}"
524
+ end
525
+ elsif next_msg.consumer_tag.nil?
526
+ @basic_gets.push next_msg
527
+ else
528
+ Thread.pass until (consumer = @consumers[next_msg.consumer_tag])
529
+ consumer.push next_msg
530
+ end
531
+ nil
532
+ ensure
533
+ @next_msg = @next_body = @next_body_size = nil
534
+ end
336
535
 
337
- args
536
+ def write_bytes(*bytes)
537
+ raise Error::Closed.new(@id, *@closed) if @closed
538
+
539
+ @connection.write_bytes(*bytes)
540
+ end
541
+
542
+ def expect(expected_frame_type)
543
+ frame_type, *args = @replies.pop
544
+ raise Error::Closed.new(@id, *@closed) if frame_type.nil?
545
+ raise Error::UnexpectedFrame.new(expected_frame_type, frame_type) unless frame_type == expected_frame_type
546
+
547
+ args
548
+ end
549
+ end
338
550
  end
339
551
  end
340
552
  end