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.
Files changed (79) hide show
  1. data/.gitignore +2 -3
  2. data/.travis.yml +5 -2
  3. data/.yardopts +2 -0
  4. data/CHANGELOG +17 -20
  5. data/Gemfile +7 -5
  6. data/README.textile +67 -29
  7. data/Rakefile +6 -0
  8. data/amqp.gemspec +5 -5
  9. data/docs/08Migration.textile +27 -0
  10. data/docs/Bindings.textile +27 -0
  11. data/docs/ConnectingToTheBroker.textile +277 -0
  12. data/docs/DocumentationGuidesIndex.textile +25 -0
  13. data/docs/Durability.textile +27 -0
  14. data/docs/ErrorHandling.textile +84 -0
  15. data/docs/Exchanges.textile +27 -0
  16. data/docs/GettingStarted.textile +585 -0
  17. data/docs/Queues.textile +27 -0
  18. data/docs/RabbitMQVersions.textile +12 -2
  19. data/docs/Routing.textile +27 -0
  20. data/docs/TLS.textile +27 -0
  21. data/docs/VendorSpecificExtensions.textile +11 -1
  22. data/examples/{various → channels}/open_channel_without_assignment.rb +0 -4
  23. data/examples/channels/prefetch_as_constructor_argument.rb +31 -0
  24. data/examples/channels/qos_aka_prefetch.rb +34 -0
  25. data/examples/channels/qos_aka_prefetch_without_callback.rb +32 -0
  26. data/examples/error_handling/channel_level_exception.rb +47 -0
  27. data/examples/error_handling/channel_level_exception_with_multiple_channels_involved.rb +54 -0
  28. data/examples/error_handling/connection_loss_handler.rb +39 -0
  29. data/examples/error_handling/global_channel_level_exception_handler.rb +65 -0
  30. data/examples/error_handling/handling_authentication_failure_with_a_callback.rb +33 -0
  31. data/examples/error_handling/tcp_connection_failure_handling_with_a_rescue_block.rb +30 -0
  32. data/examples/error_handling/tcp_connection_failure_with_a_callback.rb +28 -0
  33. data/examples/{various → exchanges}/declare_an_exchange_without_assignment.rb +0 -4
  34. data/examples/guides/getting_started/01_hello_world.rb +24 -0
  35. data/examples/guides/getting_started/02_hello_world_dslified.rb +23 -0
  36. data/examples/guides/getting_started/03_babblr.rb +33 -0
  37. data/examples/guides/getting_started/04_weathr.rb +56 -0
  38. data/examples/hello_world.rb +12 -13
  39. data/examples/hello_world_with_eventmachine_in_a_separate_thread.rb +37 -0
  40. data/examples/{various → legacy}/ack.rb +0 -0
  41. data/examples/{various → legacy}/callbacks.rb +0 -0
  42. data/examples/{various → legacy}/clock.rb +0 -0
  43. data/examples/{various → legacy}/hashtable.rb +0 -0
  44. data/examples/{various → legacy}/logger.rb +0 -0
  45. data/examples/{various → legacy}/multiclock.rb +0 -0
  46. data/examples/{various → legacy}/pingpong.rb +0 -2
  47. data/examples/{various → legacy}/primes-simple.rb +0 -0
  48. data/examples/{various → legacy}/primes.rb +0 -2
  49. data/examples/{various → legacy}/stocks.rb +0 -0
  50. data/examples/{various → queues}/automatic_binding_for_default_direct_exchange.rb +4 -0
  51. data/examples/{various → queues}/basic_get.rb +0 -2
  52. data/examples/{various → queues}/declare_a_queue_without_assignment.rb +0 -4
  53. data/examples/queues/declare_and_bind_a_server_named_queue.rb +43 -0
  54. data/examples/{various → queues}/queue_status.rb +3 -8
  55. data/examples/{various → routing}/pubsub.rb +0 -0
  56. data/examples/{various → routing}/weather_updates.rb +1 -1
  57. data/lib/amqp/channel.rb +231 -52
  58. data/lib/amqp/client.rb +6 -3
  59. data/lib/amqp/connection.rb +9 -10
  60. data/lib/amqp/deprecated/fork.rb +3 -3
  61. data/lib/amqp/deprecated/logger.rb +1 -0
  62. data/lib/amqp/deprecated/mq.rb +23 -1
  63. data/lib/amqp/deprecated/rpc.rb +1 -0
  64. data/lib/amqp/exceptions.rb +45 -3
  65. data/lib/amqp/exchange.rb +29 -35
  66. data/lib/amqp/ext/em.rb +0 -7
  67. data/lib/amqp/ext/emfork.rb +3 -2
  68. data/lib/amqp/header.rb +4 -0
  69. data/lib/amqp/queue.rb +96 -33
  70. data/lib/amqp/session.rb +140 -0
  71. data/lib/amqp/version.rb +6 -1
  72. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +7 -7
  73. data/spec/integration/channel_level_exception_with_multiple_channels_spec.rb +69 -0
  74. data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +42 -0
  75. data/spec/integration/queue_declaration_spec.rb +8 -24
  76. data/spec/integration/queue_redeclaration_with_incompatible_attributes_spec.rb +43 -0
  77. data/spec/unit/amqp/connection_spec.rb +1 -1
  78. metadata +200 -182
  79. 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