amqp 0.8.0.rc2 → 0.8.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -3
- data/.travis.yml +5 -2
- data/.yardopts +2 -0
- data/CHANGELOG +17 -20
- data/Gemfile +7 -5
- data/README.textile +67 -29
- data/Rakefile +6 -0
- data/amqp.gemspec +5 -5
- data/docs/08Migration.textile +27 -0
- data/docs/Bindings.textile +27 -0
- data/docs/ConnectingToTheBroker.textile +277 -0
- data/docs/DocumentationGuidesIndex.textile +25 -0
- data/docs/Durability.textile +27 -0
- data/docs/ErrorHandling.textile +84 -0
- data/docs/Exchanges.textile +27 -0
- data/docs/GettingStarted.textile +585 -0
- data/docs/Queues.textile +27 -0
- data/docs/RabbitMQVersions.textile +12 -2
- data/docs/Routing.textile +27 -0
- data/docs/TLS.textile +27 -0
- data/docs/VendorSpecificExtensions.textile +11 -1
- data/examples/{various → channels}/open_channel_without_assignment.rb +0 -4
- data/examples/channels/prefetch_as_constructor_argument.rb +31 -0
- data/examples/channels/qos_aka_prefetch.rb +34 -0
- data/examples/channels/qos_aka_prefetch_without_callback.rb +32 -0
- data/examples/error_handling/channel_level_exception.rb +47 -0
- data/examples/error_handling/channel_level_exception_with_multiple_channels_involved.rb +54 -0
- data/examples/error_handling/connection_loss_handler.rb +39 -0
- data/examples/error_handling/global_channel_level_exception_handler.rb +65 -0
- data/examples/error_handling/handling_authentication_failure_with_a_callback.rb +33 -0
- data/examples/error_handling/tcp_connection_failure_handling_with_a_rescue_block.rb +30 -0
- data/examples/error_handling/tcp_connection_failure_with_a_callback.rb +28 -0
- data/examples/{various → exchanges}/declare_an_exchange_without_assignment.rb +0 -4
- data/examples/guides/getting_started/01_hello_world.rb +24 -0
- data/examples/guides/getting_started/02_hello_world_dslified.rb +23 -0
- data/examples/guides/getting_started/03_babblr.rb +33 -0
- data/examples/guides/getting_started/04_weathr.rb +56 -0
- data/examples/hello_world.rb +12 -13
- data/examples/hello_world_with_eventmachine_in_a_separate_thread.rb +37 -0
- data/examples/{various → legacy}/ack.rb +0 -0
- data/examples/{various → legacy}/callbacks.rb +0 -0
- data/examples/{various → legacy}/clock.rb +0 -0
- data/examples/{various → legacy}/hashtable.rb +0 -0
- data/examples/{various → legacy}/logger.rb +0 -0
- data/examples/{various → legacy}/multiclock.rb +0 -0
- data/examples/{various → legacy}/pingpong.rb +0 -2
- data/examples/{various → legacy}/primes-simple.rb +0 -0
- data/examples/{various → legacy}/primes.rb +0 -2
- data/examples/{various → legacy}/stocks.rb +0 -0
- data/examples/{various → queues}/automatic_binding_for_default_direct_exchange.rb +4 -0
- data/examples/{various → queues}/basic_get.rb +0 -2
- data/examples/{various → queues}/declare_a_queue_without_assignment.rb +0 -4
- data/examples/queues/declare_and_bind_a_server_named_queue.rb +43 -0
- data/examples/{various → queues}/queue_status.rb +3 -8
- data/examples/{various → routing}/pubsub.rb +0 -0
- data/examples/{various → routing}/weather_updates.rb +1 -1
- data/lib/amqp/channel.rb +231 -52
- data/lib/amqp/client.rb +6 -3
- data/lib/amqp/connection.rb +9 -10
- data/lib/amqp/deprecated/fork.rb +3 -3
- data/lib/amqp/deprecated/logger.rb +1 -0
- data/lib/amqp/deprecated/mq.rb +23 -1
- data/lib/amqp/deprecated/rpc.rb +1 -0
- data/lib/amqp/exceptions.rb +45 -3
- data/lib/amqp/exchange.rb +29 -35
- data/lib/amqp/ext/em.rb +0 -7
- data/lib/amqp/ext/emfork.rb +3 -2
- data/lib/amqp/header.rb +4 -0
- data/lib/amqp/queue.rb +96 -33
- data/lib/amqp/session.rb +140 -0
- data/lib/amqp/version.rb +6 -1
- data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +7 -7
- data/spec/integration/channel_level_exception_with_multiple_channels_spec.rb +69 -0
- data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +42 -0
- data/spec/integration/queue_declaration_spec.rb +8 -24
- data/spec/integration/queue_redeclaration_with_incompatible_attributes_spec.rb +43 -0
- data/spec/unit/amqp/connection_spec.rb +1 -1
- metadata +200 -182
- data/lib/amqp/basic_client.rb +0 -27
@@ -0,0 +1,25 @@
|
|
1
|
+
h1. Ruby AMQP gem documentation guides
|
2
|
+
|
3
|
+
h2. Guide list
|
4
|
+
|
5
|
+
* {file:docs/GettingStarted.textile Getting started}
|
6
|
+
* {file:docs/ConnectingToTheBroker.textile Connecting to the broker}
|
7
|
+
* {file:docs/Queues.textile Queues}
|
8
|
+
* {file:docs/Exchanges.textile Exchanges}
|
9
|
+
* {file:docs/Bindings.textile Bindings}
|
10
|
+
* {file:docs/Routing.textile Routing}
|
11
|
+
* {file:docs/Durability.textile Exchanges, queues and message durability}
|
12
|
+
* {file:docs/ErrorHandling.textile Error handling}
|
13
|
+
* {file:docs/08Migration.textile Upgrading from version 0.6.x/0.7.x to 0.8.x and above}
|
14
|
+
* {file:docs/RabbitMQVersions.textile RabbitMQ versions}
|
15
|
+
* {file:docs/TLS.textile Using TLS (SSL)}
|
16
|
+
* {file:docs/VendorSpecificExtensions.textile Vendor-specific extensions to AMQP 0.9.1 spec}
|
17
|
+
|
18
|
+
|
19
|
+
h2. Tell us what you think!
|
20
|
+
|
21
|
+
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|
22
|
+
what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
|
23
|
+
key to making documentation better.
|
24
|
+
|
25
|
+
If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|
@@ -0,0 +1,27 @@
|
|
1
|
+
h1. TBD
|
2
|
+
|
3
|
+
|
4
|
+
h2. About this guide
|
5
|
+
|
6
|
+
TBD
|
7
|
+
|
8
|
+
|
9
|
+
h2. Covered versions
|
10
|
+
|
11
|
+
This guide covers amqp gem v0.8.0 and later.
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
h2. TBD
|
16
|
+
|
17
|
+
TBD
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
h2. Tell us what you think!
|
22
|
+
|
23
|
+
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|
24
|
+
what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
|
25
|
+
key to making documentation better.
|
26
|
+
|
27
|
+
If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|
@@ -0,0 +1,84 @@
|
|
1
|
+
h1. Handling AMQP exceptions and connection failures
|
2
|
+
|
3
|
+
h2. About this guide
|
4
|
+
|
5
|
+
Development of a robust application, be it message publisher or message consumer, involves dealing with
|
6
|
+
multiple kinds of failures: protocol exceptions, network failures, broker failures and so on.
|
7
|
+
Correct error handling and recovery is not easy. This guide explains how amqp gem helps you in dealing with
|
8
|
+
issues like
|
9
|
+
|
10
|
+
* Broker connection failures
|
11
|
+
* Network connection interruption
|
12
|
+
* TLS (SSL) related issues
|
13
|
+
* AMQP connection-level exceptions
|
14
|
+
* AMQP channel-level exceptions
|
15
|
+
* Broker failure
|
16
|
+
|
17
|
+
|
18
|
+
h2. Covered versions
|
19
|
+
|
20
|
+
This guide covers amqp gem v0.8.0 and later.
|
21
|
+
|
22
|
+
|
23
|
+
h2. Code examples
|
24
|
+
|
25
|
+
There are several {https://github.com/ruby-amqp/amqp/tree/master/examples/error_handling examples} in the git repository dedicated to the topic of error handling and recovery. Feel
|
26
|
+
free to contribute new examples.
|
27
|
+
|
28
|
+
|
29
|
+
h2. Broker connection failures
|
30
|
+
|
31
|
+
TBD
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
h2. Network connection interruption
|
36
|
+
|
37
|
+
TBD
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
h2. TLS (SSL) related issues
|
42
|
+
|
43
|
+
TBD
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
h2. AMQP connection-level exceptions
|
48
|
+
|
49
|
+
TBD
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
h2. AMQP channel-level exceptions
|
54
|
+
|
55
|
+
TBD
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
h2. Broker failure
|
60
|
+
|
61
|
+
TBD
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
h2. Recovery
|
67
|
+
|
68
|
+
TBD
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
h2. Conclusion
|
73
|
+
|
74
|
+
TBD
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
h2. Tell us what you think!
|
79
|
+
|
80
|
+
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|
81
|
+
what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
|
82
|
+
key to making documentation better.
|
83
|
+
|
84
|
+
If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|
@@ -0,0 +1,27 @@
|
|
1
|
+
h1. TBD
|
2
|
+
|
3
|
+
|
4
|
+
h2. About this guide
|
5
|
+
|
6
|
+
TBD
|
7
|
+
|
8
|
+
|
9
|
+
h2. Covered versions
|
10
|
+
|
11
|
+
This guide covers amqp gem v0.8.0 and later.
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
h2. TBD
|
16
|
+
|
17
|
+
TBD
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
h2. Tell us what you think!
|
22
|
+
|
23
|
+
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|
24
|
+
what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
|
25
|
+
key to making documentation better.
|
26
|
+
|
27
|
+
If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|
@@ -0,0 +1,585 @@
|
|
1
|
+
h1. Getting started with AMQP Ruby gem
|
2
|
+
|
3
|
+
|
4
|
+
h2. About this guide
|
5
|
+
|
6
|
+
This guide is a quick tutorial that helps you to get started with AMQP 0.9.1 in general and amqp gem in particular.
|
7
|
+
It should take about 20 minutes to read and study provided code examples. This guide covers
|
8
|
+
|
9
|
+
* Installing RabbitMQ, a mature popular implementation of multiple versions of AMQP protocol.
|
10
|
+
* Installing amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
|
11
|
+
* Running a "Hello, world"-like code example, a simple demonstration of 1:1 communication.
|
12
|
+
* Creating a "Twitter like" publish/subscribe example with 1 publisher and 4 subscribers, a case of 1:n communication.
|
13
|
+
* Creating a topic routing example with 2 publishers and 8 subscribers, a case of n:m communication when subscribers only receive messages they are interested in.
|
14
|
+
|
15
|
+
|
16
|
+
h2. Covered versions
|
17
|
+
|
18
|
+
This guide covers amqp gem v0.8.0 and later.
|
19
|
+
|
20
|
+
|
21
|
+
h2. Installing RabbitMQ
|
22
|
+
|
23
|
+
RabbitMQ site has a good "installation guide":http://www.rabbitmq.com/install.html that covers many operating systems.
|
24
|
+
On Mac OS X, the fastest way to install RabbitMQ is with Homebrew:
|
25
|
+
|
26
|
+
<code>
|
27
|
+
brew install rabbitmq
|
28
|
+
</code>
|
29
|
+
|
30
|
+
then run it:
|
31
|
+
<code>
|
32
|
+
rabbitmq-server
|
33
|
+
</code>
|
34
|
+
|
35
|
+
On "Debian and Ubuntu":http://www.rabbitmq.com/install.html#debian, you can either download a .deb package and install it with
|
36
|
+
dpkg or use apt repository RabbitMQ team provides. RabbitMQ package in even recent (10.10) versions of Ubuntu are old and won't
|
37
|
+
work with amqp gem 0.8.0 and later (we need at least version 2.0). For "RPM-based distributions":http://www.rabbitmq.com/install.html#rpm like RedHat
|
38
|
+
or CentOS RabbitMQ team provides an RPM package.
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
h2. Installing amqp gem
|
43
|
+
|
44
|
+
h3. Make sure you have Ruby installed
|
45
|
+
|
46
|
+
This guides assumes you have one of the supported Ruby implementations installed:
|
47
|
+
|
48
|
+
* Ruby 1.8.7
|
49
|
+
* Ruby 1.9.2
|
50
|
+
* JRuby (we recommend 1.6)
|
51
|
+
* Rubinius 1.2 or higher
|
52
|
+
* Ruby Enterprise Edition
|
53
|
+
|
54
|
+
|
55
|
+
h3. With Rubygems
|
56
|
+
|
57
|
+
To get amqp gem 0.8.0
|
58
|
+
<code>
|
59
|
+
gem install amqp --pre
|
60
|
+
</code>
|
61
|
+
|
62
|
+
h3. With Bundler
|
63
|
+
|
64
|
+
<code>
|
65
|
+
gem "amqp", :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
|
66
|
+
</code>
|
67
|
+
|
68
|
+
h3. Verifying your installation
|
69
|
+
|
70
|
+
Lets verify your installation with this quick irb session:
|
71
|
+
|
72
|
+
<code>
|
73
|
+
irb -rubygems
|
74
|
+
|
75
|
+
:001 > require "amqp"
|
76
|
+
=> true
|
77
|
+
:002 > AMQP::VERSION
|
78
|
+
=> "0.8.0.rc2"
|
79
|
+
</code>
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
h2. A "Hello, world" example
|
84
|
+
|
85
|
+
Lets begin with a classic Hello, world example. First, here's the code:
|
86
|
+
|
87
|
+
<code>
|
88
|
+
#!/usr/bin/env ruby
|
89
|
+
# encoding: utf-8
|
90
|
+
|
91
|
+
require "rubygems"
|
92
|
+
require "amqp"
|
93
|
+
|
94
|
+
EventMachine.run do
|
95
|
+
connection = AMQP.connect(:host => '127.0.0.1')
|
96
|
+
puts "Connected to AMQP broker. Running #{AMQP::VERSION} version of the gem..."
|
97
|
+
|
98
|
+
channel = AMQP::Channel.new(connection)
|
99
|
+
queue = channel.queue("amqpgem.examples.hello_world", :auto_delete => true)
|
100
|
+
exchange = channel.direct("")
|
101
|
+
|
102
|
+
queue.subscribe do |payload|
|
103
|
+
puts "Received a message: #{payload}. Disconnecting..."
|
104
|
+
|
105
|
+
connection.close {
|
106
|
+
EM.stop { exit }
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
exchange.publish "Hello, world!", :routing_key => queue.name
|
111
|
+
end
|
112
|
+
</code>
|
113
|
+
|
114
|
+
This example demonstrates a very common communication scenario: app A wants to publish a message that will end up in
|
115
|
+
a queue that app B listens on. In this example, queue name is "amqpgem.examples.hello". Lets go through this example
|
116
|
+
step by step:
|
117
|
+
|
118
|
+
<pre>
|
119
|
+
<code>
|
120
|
+
require "rubygems"
|
121
|
+
require "amqp"
|
122
|
+
</code>
|
123
|
+
</pre>
|
124
|
+
|
125
|
+
is the simplest way to load amqp gem if you have installed it with RubyGems. The following piece of code
|
126
|
+
|
127
|
+
<pre>
|
128
|
+
<code>
|
129
|
+
EventMachine.run do
|
130
|
+
# ...
|
131
|
+
end
|
132
|
+
</code>
|
133
|
+
</pre>
|
134
|
+
|
135
|
+
runs what is called EventMachine reactor. Without paying much attention to what exactly does reactor mean in this case,
|
136
|
+
let us say that amqp gem is asynchronous and is based on an asynchronous network I/O library called "EventMachine":http://rubyeventmachine.com.
|
137
|
+
|
138
|
+
Next line
|
139
|
+
|
140
|
+
<pre>
|
141
|
+
<code>
|
142
|
+
connection = AMQP.connect(:host => '127.0.0.1')
|
143
|
+
</code>
|
144
|
+
</pre>
|
145
|
+
|
146
|
+
connects to the server running on localhost, with default port, username, password and virtual host.
|
147
|
+
|
148
|
+
<pre>
|
149
|
+
<code>
|
150
|
+
channel = AMQP::Channel.new(connection)
|
151
|
+
</code>
|
152
|
+
</pre>
|
153
|
+
|
154
|
+
opens the channel. AMQP is a multi-channeled protocol. Channels is a way to multiplex a TCP connection.
|
155
|
+
Because channels are open on a connection, AMQP::Channel constructor takes connection object as a parameter.
|
156
|
+
|
157
|
+
This line
|
158
|
+
|
159
|
+
<pre>
|
160
|
+
<code>
|
161
|
+
queue = channel.queue("amqpgem.examples.helloworld", :auto_delete => true)
|
162
|
+
</code>
|
163
|
+
</pre>
|
164
|
+
|
165
|
+
declares a queue on the channel we've just opened. Queues are where consumer applications get messages from.
|
166
|
+
We declare this queue with "auto-delete" parameter. Basically, that means "when there is no one left
|
167
|
+
consuming messages from this queue, delete it".
|
168
|
+
|
169
|
+
The next line,
|
170
|
+
|
171
|
+
<pre>
|
172
|
+
<code>
|
173
|
+
exchange = channel.direct("")
|
174
|
+
</code>
|
175
|
+
</pre>
|
176
|
+
|
177
|
+
instantiates an exchange. Exchange is where messages are sent by producers. Exchanges route messages to queues
|
178
|
+
according to rules called bindings. In this particular example, there are no explicitly defined bindings.
|
179
|
+
Exchange we defined is known as default exchange and it has implied binding to all queues. Before we get
|
180
|
+
into that, lets see how we define a handler for incoming messages:
|
181
|
+
|
182
|
+
<pre>
|
183
|
+
<code>
|
184
|
+
queue.subscribe do |payload|
|
185
|
+
puts "Received a message: #{payload}. Disconnecting..."
|
186
|
+
|
187
|
+
connection.close {
|
188
|
+
EM.stop { exit }
|
189
|
+
}
|
190
|
+
end
|
191
|
+
</code>
|
192
|
+
</pre>
|
193
|
+
|
194
|
+
{AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes
|
195
|
+
AMQP connection and runs a callback that stops EventMachine reactor.
|
196
|
+
|
197
|
+
Finally, we publish our message:
|
198
|
+
|
199
|
+
<pre>
|
200
|
+
<code>
|
201
|
+
exchange.publish "Hello, world!", :routing_key => queue.name
|
202
|
+
</code>
|
203
|
+
</pre>
|
204
|
+
|
205
|
+
Routing key is one of _message attributes_. Default exchange will route message to a queue that has the same name
|
206
|
+
as message's routing key. This is how our message ends up in amqpgem.examples.helloworld queue.
|
207
|
+
|
208
|
+
This first example can be modified to use method chaining technique:
|
209
|
+
|
210
|
+
<pre>
|
211
|
+
<code>
|
212
|
+
#!/usr/bin/env ruby
|
213
|
+
# encoding: utf-8
|
214
|
+
|
215
|
+
require "rubygems"
|
216
|
+
require "amqp"
|
217
|
+
|
218
|
+
EventMachine.run do
|
219
|
+
AMQP.connect(:host => '127.0.0.1') do |connection|
|
220
|
+
puts "Connected to AMQP broker. Running #{AMQP::VERSION} version of the gem..."
|
221
|
+
|
222
|
+
channel = AMQP::Channel.new(connection)
|
223
|
+
|
224
|
+
channel.queue("amqpgem.examples.helloworld", :auto_delete => true).subscribe do |payload|
|
225
|
+
puts "Received a message: #{payload}. Disconnecting..."
|
226
|
+
|
227
|
+
connection.close {
|
228
|
+
EM.stop { exit }
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
channel.direct("").publish "Hello, world!", :routing_key => queue.name
|
233
|
+
end
|
234
|
+
end
|
235
|
+
</code>
|
236
|
+
</pre>
|
237
|
+
|
238
|
+
With classes and methods introduced in this example, lets move on to a little bit more
|
239
|
+
sophisticated one.
|
240
|
+
|
241
|
+
|
242
|
+
h2. Babblr: one-to-many publish/subscribe example
|
243
|
+
|
244
|
+
Previous example demonstrated how connection to the broker is made and how to do 1:1 communication
|
245
|
+
using default exchange. Now lets take a look at another common scenario: broadcast, or multiple consumers
|
246
|
+
and one producer.
|
247
|
+
|
248
|
+
A very well know example of broadcast is Twitter: every time a person tweets, followers receive a notification.
|
249
|
+
Blabbr, our imaginary information network, models this scenario this way: every network member has a separate
|
250
|
+
queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow official NBA
|
251
|
+
account on Blabbr to get updates about what is up in the world of basketball. Here is the code:
|
252
|
+
|
253
|
+
<pre>
|
254
|
+
<code>
|
255
|
+
#!/usr/bin/env ruby
|
256
|
+
# encoding: utf-8
|
257
|
+
|
258
|
+
require "rubygems"
|
259
|
+
require "amqp"
|
260
|
+
|
261
|
+
AMQP.start("amqp://dev.rabbitmq.com:5672/") do |connection|
|
262
|
+
channel = AMQP::Channel.new(connection)
|
263
|
+
exchange = channel.fanout("nba.scores")
|
264
|
+
|
265
|
+
channel.queue("joe", :auto_delete => true).bind(exchange).subscribe do |payload|
|
266
|
+
puts "#{payload} => joe"
|
267
|
+
end
|
268
|
+
|
269
|
+
channel.queue("aaron", :auto_delete => true).bind(exchange).subscribe do |payload|
|
270
|
+
puts "#{payload} => aaron"
|
271
|
+
end
|
272
|
+
|
273
|
+
channel.queue("bob", :auto_delete => true).bind(exchange).subscribe do |payload|
|
274
|
+
puts "#{payload} => bob"
|
275
|
+
end
|
276
|
+
|
277
|
+
exchange.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88")
|
278
|
+
|
279
|
+
# disconnect & exit after 1 second
|
280
|
+
EventMachine.add_timer(1) do
|
281
|
+
exchange.delete
|
282
|
+
|
283
|
+
connection.close {
|
284
|
+
EM.stop { exit }
|
285
|
+
}
|
286
|
+
end
|
287
|
+
end
|
288
|
+
</code>
|
289
|
+
</pre>
|
290
|
+
|
291
|
+
First line has a few difference from "Hello, world" example above:
|
292
|
+
|
293
|
+
* We use {AMQP.start} instead of {AMQP.connect}
|
294
|
+
* Instead of return values, we pass connection method a block and it yields connection
|
295
|
+
object back as soon as connection is established.
|
296
|
+
* Instead of passing connection parameters as a hash, we used a URI string.
|
297
|
+
|
298
|
+
{AMQP.start} is just a convenient way to do
|
299
|
+
|
300
|
+
<pre>
|
301
|
+
<code>
|
302
|
+
EventMachine.run do
|
303
|
+
AMQP.connect(options) do |connection|
|
304
|
+
# ...
|
305
|
+
end
|
306
|
+
end
|
307
|
+
</code>
|
308
|
+
</pre>
|
309
|
+
|
310
|
+
{AMQP.start} call blocks current thread so it's use is limited to scripts and small command
|
311
|
+
line applications. Blabbr is just that.
|
312
|
+
|
313
|
+
{AMQP.connect}, when invoked with a block, will yield connection object to it as soon as AMQP connection
|
314
|
+
is open. Finally, connection parameters maybe given as a Hash or as a connection string. {AMQP.connect}
|
315
|
+
method documentation has all the details.
|
316
|
+
|
317
|
+
Opening a channel in this example is no different from opening a channel in the example before that,
|
318
|
+
but exchange is instantiated differently:
|
319
|
+
|
320
|
+
<pre>
|
321
|
+
<code>
|
322
|
+
exchange = channel.fanout("nba.scores")
|
323
|
+
</code>
|
324
|
+
</pre>
|
325
|
+
|
326
|
+
Exchange we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. Fanout exchanges deliver messages to every queue that
|
327
|
+
was bound to it: exactly what we want in case of Blabbr!
|
328
|
+
|
329
|
+
This piece of code
|
330
|
+
|
331
|
+
<pre>
|
332
|
+
<code>
|
333
|
+
channel.queue("joe", :auto_delete => true).bind(exchange).subscribe do |payload|
|
334
|
+
puts "#{payload} => joe"
|
335
|
+
end
|
336
|
+
</code>
|
337
|
+
</pre>
|
338
|
+
|
339
|
+
is similar to how we subscribed for message delivery before, but what does that {AMQP::Queue#bind}
|
340
|
+
method do? It sets up a _binding_ between the queue and an exchange you pass to it. We need to do this
|
341
|
+
to make sure that our fanout exchange routes messages to follower queues.
|
342
|
+
|
343
|
+
<pre>
|
344
|
+
<code>
|
345
|
+
exchange.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88")
|
346
|
+
</code>
|
347
|
+
</pre>
|
348
|
+
|
349
|
+
demonstrates {AMQP::Exchange#publish} calls chaining. Because Blabbr members use fanout exchange
|
350
|
+
for publishing, there is no need to specify routing key: every queue that was bound to exchange receiving
|
351
|
+
a message will get it's own message copy, regardless of queue name and routing key used.
|
352
|
+
|
353
|
+
Next we use EventMachine's {http://eventmachine.rubyforge.org/EventMachine.html#M000466 add_timer} method to
|
354
|
+
run a piece of code in 1 second from now:
|
355
|
+
|
356
|
+
<pre>
|
357
|
+
<code>
|
358
|
+
EventMachine.add_timer(1) do
|
359
|
+
exchange.delete
|
360
|
+
|
361
|
+
connection.close {
|
362
|
+
EM.stop { exit }
|
363
|
+
}
|
364
|
+
end
|
365
|
+
</code>
|
366
|
+
</pre>
|
367
|
+
|
368
|
+
The code we want to run deletes exchange we declared earlier using {AMQP::Exchange#delete} and closes AMQP
|
369
|
+
connection with {AMQP::Session#close}. Finally, we stop EventMachine event loop and exit.
|
370
|
+
|
371
|
+
Blabbr is pretty unlikely to secure hundreds of millions in funding but it does a pretty good job of
|
372
|
+
demonstrating how one can use AMQP fanout exchanges to do broadcasting.
|
373
|
+
|
374
|
+
|
375
|
+
|
376
|
+
h2. Weathr: many-to-many topic routing example
|
377
|
+
|
378
|
+
So far we have seen point-to-point communication and broadcast. These two are possible with many protocols:
|
379
|
+
HTTP handles these scenarios just fine. What differentiates AMQP? Next is going to introduce you to topic
|
380
|
+
exchanges and routing with patterns, one of the features that makes AMQP very powerful.
|
381
|
+
|
382
|
+
Our third example is weather condition updates. What makes it different from the previous two is that
|
383
|
+
not all consumers are interested in all messages: people who live in Portland usually don't care about
|
384
|
+
weather in Hong Kong very much (unless they are going there soon). They are certainly interested in
|
385
|
+
weather conditions around Portland, possibly all of Oregon and sometimes a few neighbouring states.
|
386
|
+
|
387
|
+
Our example features multiple consumer applications monitoring updates for different regions. Some are
|
388
|
+
interested in updates for a specific city, others for a specific state and so on all the way up to continents.
|
389
|
+
Updates may overlap: an update for San Diego, CA _is_ an update for California, and should certainly show up
|
390
|
+
on North America updates list.
|
391
|
+
|
392
|
+
Here is the code:
|
393
|
+
|
394
|
+
<pre>
|
395
|
+
<code>
|
396
|
+
#!/usr/bin/env ruby
|
397
|
+
# encoding: utf-8
|
398
|
+
|
399
|
+
require "rubygems"
|
400
|
+
require "amqp"
|
401
|
+
|
402
|
+
EventMachine.run do
|
403
|
+
AMQP.connect do |connection|
|
404
|
+
channel = AMQP::Channel.new(connection)
|
405
|
+
exchange = channel.topic("pub/sub", :auto_delete => true)
|
406
|
+
|
407
|
+
# Subscribers.
|
408
|
+
channel.queue("", :exclusive => true) do |queue|
|
409
|
+
queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
|
410
|
+
puts "An update for North America: #{payload}, routing key is #{headers.routing_key}"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
|
414
|
+
puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
|
415
|
+
end
|
416
|
+
channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |headers, payload|
|
417
|
+
puts "An update for US/California: #{payload}, routing key is #{headers.routing_key}"
|
418
|
+
end
|
419
|
+
channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
|
420
|
+
puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
|
421
|
+
end
|
422
|
+
channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |headers, payload|
|
423
|
+
puts "An update for Rome, Italy: #{payload}, routing key is #{headers.routing_key}"
|
424
|
+
end
|
425
|
+
channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |headers, payload|
|
426
|
+
puts "An update for Hong Kong: #{payload}, routing key is #{headers.routing_key}"
|
427
|
+
end
|
428
|
+
|
429
|
+
EM.add_timer(1) do
|
430
|
+
exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego").
|
431
|
+
publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley").
|
432
|
+
publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco").
|
433
|
+
publish("New York update", :routing_key => "americas.north.us.ny.newyork").
|
434
|
+
publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo").
|
435
|
+
publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong").
|
436
|
+
publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto").
|
437
|
+
publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai").
|
438
|
+
publish("Rome update", :routing_key => "europe.italy.roma").
|
439
|
+
publish("Paris update", :routing_key => "europe.france.paris")
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
show_stopper = Proc.new {
|
444
|
+
connection.close do
|
445
|
+
EM.stop
|
446
|
+
end
|
447
|
+
}
|
448
|
+
|
449
|
+
EM.add_timer(2, show_stopper)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
</code>
|
453
|
+
</pre>
|
454
|
+
|
455
|
+
First line that is different from Blabbr example is
|
456
|
+
|
457
|
+
<pre>
|
458
|
+
<code>
|
459
|
+
exchange = channel.topic("pub/sub", :auto_delete => true)
|
460
|
+
</code>
|
461
|
+
</pre>
|
462
|
+
|
463
|
+
We use a _topic exchange_ here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
|
464
|
+
where consumers indicate what topics they are interested in (think of it as of subscribing to a feed for individual tag
|
465
|
+
of your favourite blog as opposed to full feed). They do it by specifying _routing pattern_ on binding, for example:
|
466
|
+
|
467
|
+
<pre>
|
468
|
+
<code>
|
469
|
+
channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
|
470
|
+
puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
|
471
|
+
end
|
472
|
+
</code>
|
473
|
+
</pre>
|
474
|
+
|
475
|
+
Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using {AMQP::Queue#bind} method.
|
476
|
+
This means that only messages with routing key matching americas.south.# will be routed to that queue. Routing pattern consists of several words
|
477
|
+
separated by dots, similarly to URI path segments joined by slash. A few of examples:
|
478
|
+
|
479
|
+
* asia.southeast.thailand.bangkok
|
480
|
+
* sports.basketball
|
481
|
+
* usa.nasdaq.aapl
|
482
|
+
* tasks.search.indexing.accounts
|
483
|
+
|
484
|
+
Now lets take a look at a few routing keys that do match "americas.south.#" pattern:
|
485
|
+
|
486
|
+
* americas.south
|
487
|
+
* americas.south.*brazil*
|
488
|
+
* americas.south.*brazil.saopaolo*
|
489
|
+
* americas.south.*chile.santiago*
|
490
|
+
|
491
|
+
In other words, # part of the pattern matches 0 or more words. For "americas.south.*", some of matching routing keys are
|
492
|
+
|
493
|
+
* americas.south.*brazil*
|
494
|
+
* americas.south.*chile*
|
495
|
+
* americas.south.*peru*
|
496
|
+
|
497
|
+
but not
|
498
|
+
|
499
|
+
* americas.south
|
500
|
+
* americas.south.chile.santiago
|
501
|
+
|
502
|
+
so * matches a single word, whatever it is. AMQP 0.9.1 spec says that topic segments (words) may contain the letters A-Z and a-z
|
503
|
+
and digits 0-9.
|
504
|
+
|
505
|
+
One more thing that is different from previous examples is that the block we pass to {AMQP::Queue#subscribe} now takes two arguments:
|
506
|
+
header and body (aka payload). Long story short, the _header_ parameter lets you access metadata associated with the message. Some
|
507
|
+
examples of message metadata attributes are
|
508
|
+
|
509
|
+
* message content type
|
510
|
+
* message content encoding
|
511
|
+
* message priority
|
512
|
+
* message expiration time
|
513
|
+
* message identifier
|
514
|
+
* reply to, to what message this message is a reply to
|
515
|
+
* application id, identifier of application that produced the message
|
516
|
+
|
517
|
+
and so on.
|
518
|
+
|
519
|
+
As this binding demonstrates, # (and *) can appear in the beginning of routing patterns, too:
|
520
|
+
|
521
|
+
<pre>
|
522
|
+
<code>
|
523
|
+
channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
|
524
|
+
puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
|
525
|
+
end
|
526
|
+
</code>
|
527
|
+
</pre>
|
528
|
+
|
529
|
+
Publishing of messages is not different from previous examples. Running this example demonstrates that, for example,
|
530
|
+
message published with routing key of "americas.north.us.ca.berkeley" is routed to several queues: us.california and
|
531
|
+
_server-named queue_ we declared by passing blank string as the name:
|
532
|
+
|
533
|
+
<pre>
|
534
|
+
<code>
|
535
|
+
channel.queue("", :exclusive => true) do |queue|
|
536
|
+
queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
|
537
|
+
puts "An update for North America: #{payload}, routing key is #{headers.routing_key}"
|
538
|
+
end
|
539
|
+
end
|
540
|
+
</code>
|
541
|
+
</pre>
|
542
|
+
|
543
|
+
Name of server-named queue is generated by the broker and sent back to the client with queue declaration confirmation.
|
544
|
+
Because of queue name is not known before reply arrives, we passed {AMQP::Channel#queue} a callback and it yielded us back
|
545
|
+
a queue object once confirmation has arrived.
|
546
|
+
|
547
|
+
|
548
|
+
h3. Avoid race conditions
|
549
|
+
|
550
|
+
A word of warning: you may find examples on the Web of {AMQP::Channel#queue} usage that do not use
|
551
|
+
callback: we *strongly recommend you always use a callback for server-named queues*. Otherwise your code may be a subject
|
552
|
+
to "race conditions":http://en.wikipedia.org/wiki/Race_condition and even though amqp gem tries to be reasonably smart and protect you from most common problems, there
|
553
|
+
is no way it can do so for every case. The only reason we support {AMQP::Channel#queue} usage w/o a callback for server-named queues is
|
554
|
+
backwards compatibility with earlier versions.
|
555
|
+
|
556
|
+
|
557
|
+
h2. Wrapping up
|
558
|
+
|
559
|
+
This tutorial ends here. Congratulations! You have learned quite a bit about both AMQP 0.9.1 and amqp gem.
|
560
|
+
|
561
|
+
|
562
|
+
h2. What to read next
|
563
|
+
|
564
|
+
Documentation is organized as a {file:docs/DocumentationGuidesIndex.textile Routing guide number of guides}, covering all kinds of
|
565
|
+
topics from {file:docs/Routing.textile routing} to {file:docs/ErrorHandling.textile error handling} to
|
566
|
+
{file:docs/VendorSpecificExchanges.textile Broker-specific AMQP 0.9.1 extensions}.
|
567
|
+
|
568
|
+
To learn more on what you have seen in this tutorial, check out
|
569
|
+
|
570
|
+
* {file:docs/ConnectingToTheBroker.textile Connection to the broker}
|
571
|
+
* {file:docs/Queues.textile Queues}
|
572
|
+
* {file:docs/Exchanges.textile Exchanges}
|
573
|
+
* {file:docs/Bindings.textile Bindings}
|
574
|
+
|
575
|
+
If you are migrating your application from earlier versions of amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is
|
576
|
+
{file:docs/08Migration.textile amqp gem 0.8 migration guide}.
|
577
|
+
|
578
|
+
|
579
|
+
h2. Tell us what you think!
|
580
|
+
|
581
|
+
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|
582
|
+
what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
|
583
|
+
key to making documentation better.
|
584
|
+
|
585
|
+
If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|