amqp-client 1.0.0 → 1.1.1
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 +4 -4
- data/.github/workflows/docs.yml +25 -0
- data/.github/workflows/main.yml +1 -2
- data/.rubocop.yml +13 -2
- data/.rubocop_todo.yml +14 -58
- data/.yardopts +1 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile +4 -0
- data/README.md +62 -25
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +487 -275
- data/lib/amqp/client/connection.rb +438 -357
- data/lib/amqp/client/errors.rb +46 -25
- data/lib/amqp/client/exchange.rb +66 -0
- data/lib/amqp/client/frame_bytes.rb +533 -0
- data/lib/amqp/client/message.rb +103 -7
- data/lib/amqp/client/properties.rb +263 -166
- data/lib/amqp/client/queue.rb +72 -0
- data/lib/amqp/client/table.rb +137 -107
- data/lib/amqp/client/version.rb +2 -1
- data/lib/amqp/client.rb +148 -97
- data/sig/amqp-client.rbs +264 -0
- metadata +9 -4
- data/lib/amqp/client/frames.rb +0 -531
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e248898d176ad9c3d785226ed37c0335052ef701ee16a16bff483089be65dd8
|
4
|
+
data.tar.gz: 89b58399edb11b30b05db86e9e4b0a617438e3ed52e63b3e76ae3a4c8cb361fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/.github/workflows/main.yml
CHANGED
@@ -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.
|
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:
|
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
|
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:
|
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:
|
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:
|
18
|
+
Max: 40
|
26
19
|
|
27
|
-
# Offense count:
|
20
|
+
# Offense count: 3
|
28
21
|
# Configuration parameters: CountBlocks.
|
29
22
|
Metrics/BlockNesting:
|
30
23
|
Max: 4
|
31
24
|
|
32
|
-
# Offense count:
|
25
|
+
# Offense count: 5
|
33
26
|
# Configuration parameters: CountComments, CountAsOne.
|
34
27
|
Metrics/ClassLength:
|
35
|
-
Max:
|
28
|
+
Max: 400
|
36
29
|
|
37
30
|
# Offense count: 9
|
38
31
|
# Configuration parameters: IgnoredMethods.
|
39
32
|
Metrics/CyclomaticComplexity:
|
40
|
-
Max:
|
33
|
+
Max: 44
|
41
34
|
|
42
|
-
# Offense count:
|
35
|
+
# Offense count: 40
|
43
36
|
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
|
44
37
|
Metrics/MethodLength:
|
45
|
-
Max:
|
38
|
+
Max: 170
|
46
39
|
|
47
|
-
# Offense count:
|
40
|
+
# Offense count: 2
|
48
41
|
# Configuration parameters: CountComments, CountAsOne.
|
49
42
|
Metrics/ModuleLength:
|
50
|
-
Max:
|
51
|
-
|
52
|
-
# Offense count: 5
|
53
|
-
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
54
|
-
Metrics/ParameterLists:
|
55
|
-
Max: 7
|
43
|
+
Max: 500
|
56
44
|
|
57
|
-
# Offense count:
|
45
|
+
# Offense count: 4
|
58
46
|
# Configuration parameters: IgnoredMethods.
|
59
47
|
Metrics/PerceivedComplexity:
|
60
|
-
Max:
|
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
data/README.md
CHANGED
@@ -1,54 +1,67 @@
|
|
1
1
|
# AMQP::Client
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
9
|
+
## Support
|
14
10
|
|
15
|
-
|
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
|
-
|
13
|
+
## Documentation
|
18
14
|
|
19
|
-
|
15
|
+
[API reference](https://cloudamqp.github.io/amqp-client.rb/)
|
20
16
|
|
21
17
|
## Usage
|
22
18
|
|
23
|
-
|
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
|
-
|
29
|
-
conn =
|
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
|
-
|
33
|
-
|
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
|
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
|
-
|
41
|
-
|
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
|
-
|
57
|
+
myqueue = amqp.queue("myqueue")
|
45
58
|
|
46
59
|
# Bind the queue to any exchange, with any binding key
|
47
|
-
|
60
|
+
myqueue.bind("amq.topic", "my.events.*")
|
48
61
|
|
49
|
-
# The message will be reprocessed if the client loses connection to the
|
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
|
-
|
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
|
-
|
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
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.
|
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"
|
data/lib/amqp/client/channel.rb
CHANGED
@@ -3,338 +3,550 @@
|
|
3
3
|
require_relative "./message"
|
4
4
|
|
5
5
|
module AMQP
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
36
|
+
# Channel ID
|
37
|
+
# @return [Integer]
|
38
|
+
attr_reader :id
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
207
|
-
|
208
|
-
expect :basic_recover_ok
|
209
|
-
end
|
395
|
+
# @!endgroup
|
396
|
+
# @!group Confirm
|
210
397
|
|
211
|
-
|
212
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
224
|
-
|
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
|
-
|
231
|
-
|
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
|
-
|
245
|
-
@
|
246
|
-
|
247
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
end
|
436
|
+
ok = ack_or_nack == :ack
|
437
|
+
@unconfirmed_empty.push(ok) until @unconfirmed_empty.num_waiting.zero?
|
438
|
+
end
|
253
439
|
|
254
|
-
|
255
|
-
|
256
|
-
expect :tx_commit_ok
|
257
|
-
end
|
440
|
+
# @!endgroup
|
441
|
+
# @!group Transaction
|
258
442
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
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
|
-
|
273
|
-
@next_msg = ReturnMessage.new(reply_code, reply_text, exchange, routing_key, nil, "")
|
274
|
-
end
|
467
|
+
# @!endgroup
|
275
468
|
|
276
|
-
|
277
|
-
|
278
|
-
|
469
|
+
# @api private
|
470
|
+
def reply(args)
|
471
|
+
@replies.push(args)
|
472
|
+
end
|
279
473
|
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
-
|
295
|
-
|
296
|
-
|
484
|
+
# @api private
|
485
|
+
def basic_get_empty
|
486
|
+
@basic_gets.push :basic_get_empty
|
487
|
+
end
|
297
488
|
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
327
|
-
|
509
|
+
# @api private
|
510
|
+
def close_consumer(tag)
|
511
|
+
@consumers.fetch(tag).close
|
512
|
+
nil
|
513
|
+
end
|
328
514
|
|
329
|
-
|
330
|
-
end
|
515
|
+
private
|
331
516
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
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
|
-
|
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
|