amqp 0.8.0.rc13 → 0.8.0.rc14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.rspec +2 -1
  2. data/.travis.yml +8 -2
  3. data/.yardopts +1 -0
  4. data/CHANGELOG +9 -0
  5. data/Gemfile +17 -11
  6. data/README.md +26 -16
  7. data/amqp.gemspec +2 -2
  8. data/bin/ci/before_build.sh +21 -0
  9. data/docs/08Migration.textile +199 -5
  10. data/docs/AMQP091ModelExplained.textile +322 -0
  11. data/docs/Bindings.textile +24 -4
  12. data/docs/Clustering.textile +1 -1
  13. data/docs/ConnectingToTheBroker.textile +98 -82
  14. data/docs/ConnectionEncryptionWithTLS.textile +65 -5
  15. data/docs/DocumentationGuidesIndex.textile +93 -13
  16. data/docs/Durability.textile +1 -1
  17. data/docs/ErrorHandling.textile +458 -94
  18. data/docs/Exchanges.textile +901 -87
  19. data/docs/GettingStarted.textile +278 -143
  20. data/docs/PatternsAndUseCases.textile +420 -0
  21. data/docs/Queues.textile +730 -178
  22. data/docs/RabbitMQVersions.textile +18 -3
  23. data/docs/RunningTests.textile +1 -1
  24. data/docs/TestingWithEventedSpec.textile +121 -0
  25. data/docs/Troubleshooting.textile +15 -1
  26. data/docs/VendorSpecificExtensions.textile +1 -1
  27. data/docs/diagrams/001_hello_world_example_routing.png +0 -0
  28. data/docs/diagrams/002_blabbr_example_routing.png +0 -0
  29. data/docs/diagrams/003_weathr_example_routing.png +0 -0
  30. data/docs/diagrams/004_fanout_exchange.png +0 -0
  31. data/docs/diagrams/005_direct_exchange.png +0 -0
  32. data/docs/diagrams/redhat/direct_exchange.png +0 -0
  33. data/docs/diagrams/redhat/fanout_exchange.png +0 -0
  34. data/docs/diagrams/redhat/topic_exchange.png +0 -0
  35. data/examples/error_handling/automatic_recovery_of_channel_and_queues.rb +50 -0
  36. data/examples/error_handling/automatically_recovering_hello_world_consumer.rb +51 -0
  37. data/examples/error_handling/automatically_recovering_hello_world_consumer_that_uses_a_server_named_queue.rb +51 -0
  38. data/examples/error_handling/basic_connection_failover.rb +22 -0
  39. data/examples/error_handling/channel_level_exception.rb +9 -2
  40. data/examples/error_handling/connection_level_exception.rb +8 -1
  41. data/examples/error_handling/connection_level_exception_with_objects.rb +49 -0
  42. data/examples/error_handling/connection_loss_handler.rb +1 -5
  43. data/examples/error_handling/hello_world_producer.rb +43 -0
  44. data/examples/error_handling/insufficient_permissions.rb +54 -0
  45. data/examples/error_handling/manual_connection_and_channel_recovery.rb +71 -0
  46. data/examples/error_handling/queue_exclusivity_violation.rb +41 -0
  47. data/examples/error_handling/queue_name_violation.rb +31 -0
  48. data/examples/exchanges/autodeletion_of_exchanges.rb +1 -4
  49. data/examples/guides/queues/01a_declaring_a_server_named_queue_using_queue_constructor.rb +7 -8
  50. data/examples/guides/queues/01b_declaring_a_queue_using_queue_constructor.rb +7 -8
  51. data/examples/guides/queues/02a_declaring_a_durable_shared_queue.rb +5 -8
  52. data/examples/guides/queues/02b_declaring_a_durable_shared_queue.rb +5 -8
  53. data/examples/guides/queues/03a_declaring_a_temporary_exclusive_queue.rb +7 -8
  54. data/examples/guides/queues/04_bind_a_queue_using_exchange_instance.rb +9 -10
  55. data/examples/guides/queues/05_bind_a_queue_using_exchange_name.rb +8 -10
  56. data/examples/guides/queues/06_subscribe_to_receive_messages.rb +10 -12
  57. data/examples/guides/queues/07_fetch_a_message_from_the_queue.rb +14 -14
  58. data/examples/guides/queues/08_unsubscribing_a_consumer.rb +13 -16
  59. data/examples/guides/queues/09_unbinding_from_exchange.rb +16 -22
  60. data/examples/guides/queues/10_purge_a_queue.rb +13 -18
  61. data/examples/guides/queues/11_deleting_a_queue.rb +14 -19
  62. data/examples/guides/queues/12_objects_that_consume_messages.rb +69 -0
  63. data/examples/guides/queues/13_objects_that_consume_messages_take_two.rb +89 -0
  64. data/examples/hello_world.rb +1 -3
  65. data/examples/hello_world_with_an_empty_string.rb +5 -6
  66. data/examples/inspecting_server_information.rb +45 -0
  67. data/examples/issues/issue_93.rb +23 -0
  68. data/examples/issues/issue_94.rb +23 -0
  69. data/examples/patterns/command/consumer.rb +45 -0
  70. data/examples/patterns/command/producer.rb +26 -0
  71. data/examples/patterns/request_reply/client.rb +29 -0
  72. data/examples/patterns/request_reply/server.rb +26 -0
  73. data/examples/publishing/publishing_a_one_off_message.rb +6 -4
  74. data/examples/publishing/returned_messages.rb +2 -10
  75. data/examples/queues/accessing_message_metadata.rb +15 -13
  76. data/examples/queues/queue_status.rb +12 -15
  77. data/examples/routing/fanout_routing.rb +33 -0
  78. data/examples/routing/headers_routing.rb +17 -15
  79. data/examples/routing/round_robin_with_direct_exchange.rb +39 -0
  80. data/examples/routing/round_robin_with_the_default_exchange.rb +38 -0
  81. data/examples/routing/unroutable_mandatory_message_is_returned.rb +33 -0
  82. data/examples/routing/weather_updates.rb +15 -20
  83. data/examples/tls/using_tls.rb +41 -0
  84. data/lib/amqp/bit_set.rb +80 -0
  85. data/lib/amqp/broker.rb +72 -0
  86. data/lib/amqp/channel.rb +93 -13
  87. data/lib/amqp/client.rb +11 -22
  88. data/lib/amqp/compatibility/ruby187_patchlevel_check.rb +2 -0
  89. data/lib/amqp/connection.rb +2 -3
  90. data/lib/amqp/consumer.rb +208 -0
  91. data/lib/amqp/deprecated/fork.rb +2 -0
  92. data/lib/amqp/deprecated/mq.rb +2 -0
  93. data/lib/amqp/exchange.rb +6 -4
  94. data/lib/amqp/extensions/rabbitmq.rb +3 -1
  95. data/lib/amqp/header.rb +76 -14
  96. data/lib/amqp/int_allocator.rb +96 -0
  97. data/lib/amqp/logger.rb +2 -0
  98. data/lib/amqp/queue.rb +242 -86
  99. data/lib/amqp/rpc.rb +2 -0
  100. data/lib/amqp/session.rb +169 -9
  101. data/lib/amqp/utilities/event_loop_helper.rb +2 -0
  102. data/lib/amqp/utilities/server_type.rb +2 -0
  103. data/lib/amqp/version.rb +2 -2
  104. data/lib/mq.rb +4 -2
  105. data/lib/mq/logger.rb +3 -1
  106. data/lib/mq/rpc.rb +3 -1
  107. data/spec/integration/authentication_spec.rb +17 -10
  108. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +1 -1
  109. data/spec/integration/automatic_recovery_predicate_spec.rb +68 -0
  110. data/spec/integration/basic_get_spec.rb +2 -1
  111. data/spec/integration/{extensions/basic_return_spec.rb → basic_return_spec.rb} +2 -1
  112. data/spec/integration/channel_level_exception_handling_spec.rb +53 -0
  113. data/spec/integration/connection_level_exception_handling_spec.rb +49 -0
  114. data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +38 -17
  115. data/spec/integration/declare_one_hundred_server_named_queues_spec.rb +44 -0
  116. data/spec/integration/direct_exchange_routing_spec.rb +125 -0
  117. data/spec/integration/exchange_declaration_spec.rb +75 -46
  118. data/spec/integration/extensions/rabbitmq/publisher_confirmations_spec.rb +180 -0
  119. data/spec/integration/{workload_distribution_spec.rb → fanout_exchange_routing_spec.rb} +10 -9
  120. data/spec/integration/headers_exchange_routing_spec.rb +269 -0
  121. data/spec/integration/hello_world_spec.rb +77 -0
  122. data/spec/integration/immediate_messages_spec.rb +59 -0
  123. data/spec/integration/mandatory_messages_spec.rb +52 -0
  124. data/spec/integration/message_metadata_access_spec.rb +106 -0
  125. data/spec/integration/multiple_consumers_per_queue_spec.rb +319 -0
  126. data/spec/integration/ordering_of_published_messages_spec.rb +96 -0
  127. data/spec/integration/queue_declaration_spec.rb +8 -8
  128. data/spec/integration/queue_status_spec.rb +66 -0
  129. data/spec/integration/recovery/per_channel_automatic_recovery_on_graceful_broker_shutdown_spec.rb +76 -0
  130. data/spec/integration/recovery/per_channel_automatic_recovery_spec.rb +72 -0
  131. data/spec/integration/redelivery_of_unacknowledged_messages_spec.rb +96 -0
  132. data/spec/integration/regressions/concurrent_publishing_on_the_same_channel_spec.rb +91 -0
  133. data/spec/integration/regressions/empty_message_body_spec.rb +56 -0
  134. data/spec/integration/regressions/issue66_spec.rb +2 -1
  135. data/spec/integration/reply_queue_communication_spec.rb +2 -1
  136. data/spec/integration/store_and_forward_spec.rb +4 -3
  137. data/spec/integration/topic_subscription_spec.rb +2 -1
  138. data/spec/integration/tx_commit_spec.rb +124 -0
  139. data/spec/integration/tx_rollback_spec.rb +167 -0
  140. data/spec/spec_helper.rb +44 -71
  141. data/spec/unit/amqp/bit_set_spec.rb +127 -0
  142. data/spec/unit/amqp/channel_id_allocation_spec.rb +40 -0
  143. data/spec/unit/amqp/connection_spec.rb +4 -2
  144. data/spec/unit/amqp/int_allocator_spec.rb +116 -0
  145. metadata +92 -26
  146. data/CONTRIBUTORS +0 -29
  147. data/docs/Routing.textile +0 -30
  148. data/examples/real-world/task-queue/README.textile +0 -3
  149. data/examples/real-world/task-queue/consumer.rb +0 -27
  150. data/examples/real-world/task-queue/producer.rb +0 -22
  151. data/spec/unit/amqp/basic_spec.rb +0 -39
  152. data/tasks.rb +0 -4
@@ -1,28 +1,29 @@
1
- # @title Ruby AMQP gem: Getting Started with AMQP and Ruby
1
+ # @title Ruby amqp gem: Getting Started with AMQP and Ruby
2
2
 
3
- h1. Getting started with AMQP Ruby gem
3
+ h1. Getting started with the Ruby amqp gem
4
4
 
5
5
 
6
6
  h2. About this guide
7
7
 
8
- This guide is a quick tutorial that helps you to get started with AMQP 0.9.1 in general and amqp gem in particular.
9
- It should take about 20 minutes to read and study provided code examples. This guide covers
8
+ This guide is a quick tutorial that helps you to get started with v0.9.1 of the AMQP specification in general and the "Ruby amqp gem":http://github.com/ruby-amqp/amqp in particular.
9
+ It should take about 20 minutes to read and study the provided code examples. This guide covers:
10
10
 
11
- * Installing RabbitMQ, a mature popular implementation of multiple versions of AMQP protocol.
12
- * Installing amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
13
- * Running the "Hello, world" of messaging, a simple demonstration of 1:1 communication.
14
- * Creating a "Twitter like" publish/subscribe example with 1 publisher and 4 subscribers, a case of 1:n communication.
15
- * 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.
11
+ * Installing RabbitMQ, a mature popular server implementation of the AMQP protocol.
12
+ * Installing the amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
13
+ * Running a "Hello, world" messaging example that is a simple demonstration of 1:1 communication.
14
+ * Creating a "Twitter-like" publish/subscribe example with 1 publisher and 4 subscribers that demonstrates 1:n communication.
15
+ * Creating a topic routing example with 2 publishers and 8 subscribers showcasing n:m communication when subscribers only receive messages that they are interested in.
16
+ * Learning how the amqp gem can be integrated with Ruby objects in a way that makes unit testing easy.
16
17
 
17
18
 
18
- h2. Covered versions
19
+ h2. Which versions of the amqp gem does this guide cover?
19
20
 
20
- This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later.
21
+ This guide covers v0.8.0 and later of the "Ruby amqp gem":https://github.com/ruby-amqp/amqp.
21
22
 
22
23
 
23
24
  h2. Installing RabbitMQ
24
25
 
25
- "RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that covers many operating systems.
26
+ The "RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that addresses many operating systems.
26
27
  On Mac OS X, the fastest way to install RabbitMQ is with "Homebrew":http://mxcl.github.com/homebrew/:
27
28
 
28
29
  <pre>
@@ -39,59 +40,56 @@ rabbitmq-server
39
40
  </code>
40
41
  </pre>
41
42
 
42
- On Debian and Ubuntu, you can either "download RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
43
- "dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or use "apt repository RabbitMQ team provides":http://www.rabbitmq.com/debian.html#apt.
44
- For RPM-based distributions like RedHat or CentOS RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
43
+ On Debian and Ubuntu, you can either "download the RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
44
+ "dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or make use of the "apt repository":http://www.rabbitmq.com/debian.html#apt that the RabbitMQ team provides.
45
+ For RPM-based distributions like RedHat or CentOS, the RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
45
46
 
46
47
  <span class="note">
47
- RabbitMQ package in even recent (10.10) versions of Ubuntu are outdated and *won't work with amqp gem 0.8.0 and later* (we need at least version 2.0).
48
+ The RabbitMQ package that ships with recent Ubuntu 10.10 versions is outdated and *will not work with v0.8.0 and later of the amqp gem* (we need at least RabbitMQ v2.0 for use with this guide).
48
49
  </span>
49
50
 
50
51
 
51
52
 
52
- h2. Installing Ruby amqp gem
53
+ h2. Installing the Ruby amqp gem
53
54
 
54
- h3. Make sure you have Ruby installed
55
+ h3. Make sure that you have Ruby and "Rubygems":http://docs.rubygems.org/read/chapter/3 installed
55
56
 
56
- This guides assumes you have one of the supported Ruby implementations installed:
57
+ This guide assumes that you have installed one of the following supported Ruby implementations:
57
58
 
58
- * Ruby 1.8.7
59
- * Ruby 1.9.2
60
- * JRuby (we recommend 1.6)
61
- * Rubinius 1.2 or higher
59
+ * Ruby v1.8.7
60
+ * Ruby v1.9.2
61
+ * JRuby (we recommend v1.6)
62
+ * Rubinius v1.2 or higher
62
63
  * Ruby Enterprise Edition
63
64
 
65
+ h3. You can use Rubygems to install the amqp gem
64
66
 
65
- h3. With Rubygems
66
-
67
- To get amqp gem 0.8.0
68
-
69
- h4. On Microsoft Windows 7
67
+ h4. On Microsoft Windows 7:
70
68
 
71
69
  <pre>
72
70
  gem install eventmachine --pre
73
- gem install amqp --pre --version "~> 0.8.0.RC12"
71
+ gem install amqp --pre
74
72
  </pre>
75
73
 
76
74
  h4. On other OSes or JRuby:
77
75
 
78
76
  <pre>
79
- gem install amqp --pre --version "~> 0.8.0.RC12"
77
+ gem install amqp --pre
80
78
  </pre>
81
79
 
82
- h3. With Bundler
80
+ h3. You can also use Bundler to install the gem
83
81
 
84
82
  <pre>
85
83
  <code>
86
84
  source :rubygems
87
85
 
88
- gem "amqp", "~> 0.8.0.RC12" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
86
+ gem "amqp", "~> 0.8.0.RC13" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
89
87
  </code>
90
88
  </pre>
91
89
 
92
90
  h3. Verifying your installation
93
91
 
94
- Lets verify your installation with this quick irb session:
92
+ Let us verify your installation with this quick irb session:
95
93
 
96
94
  <pre>
97
95
  <code>
@@ -100,21 +98,21 @@ irb -rubygems
100
98
  :001 > require "amqp"
101
99
  => true
102
100
  :002 > AMQP::VERSION
103
- => "0.8.0.rc12"
101
+ => "0.8.0.rc13"
104
102
  </code>
105
103
  </pre>
106
104
 
107
105
 
108
106
  h2. "Hello, world" example
109
107
 
110
- Lets begin with the classic "Hello, world" example. First, here's the code:
108
+ Let us begin with the classic "Hello, world" example. First, here is the code:
111
109
 
112
110
  <script src="https://gist.github.com/998690.js"> </script>
113
111
 
114
112
  (if the example above isn't displayed, see this "gist":https://gist.github.com/998690)
115
113
 
116
- This example demonstrates a very common communication scenario: app A wants to publish a message that will end up in
117
- a queue that app B listens on. In this example, queue name is "amqpgem.examples.hello". Lets go through this example
114
+ This example demonstrates a very common communication scenario: *application A* wants to publish a message that will end up in
115
+ a queue that *application B* listens on. In this case, the queue name is "amqpgem.examples.hello". Let us go through the code
118
116
  step by step:
119
117
 
120
118
  <pre>
@@ -124,7 +122,7 @@ require "amqp"
124
122
  </code>
125
123
  </pre>
126
124
 
127
- is the simplest way to load amqp gem if you have installed it with RubyGems. The following piece of code
125
+ is the simplest way to load the amqp gem if you have installed it with RubyGems. The following piece of code
128
126
 
129
127
  <pre>
130
128
  <code>
@@ -134,10 +132,10 @@ end
134
132
  </code>
135
133
  </pre>
136
134
 
137
- runs what is called EventMachine reactor. Without paying much attention to what exactly does reactor mean in this case,
138
- let us say that amqp gem is asynchronous and is based on an asynchronous network I/O library called "EventMachine":http://rubyeventmachine.com.
135
+ runs what is called the "EventMachine":http://rubyeventmachine.com reactor. We will not go into what the term 'reactor' means here, but suffice it to say
136
+ that the amqp gem is asynchronous and is based on an asynchronous network I/O library called _EventMachine_.
139
137
 
140
- Next line
138
+ The next line
141
139
 
142
140
  <pre>
143
141
  <code>
@@ -145,7 +143,9 @@ connection = AMQP.connect(:host => '127.0.0.1')
145
143
  </code>
146
144
  </pre>
147
145
 
148
- connects to the server running on localhost, with default port, username, password and virtual host.
146
+ connects to the server running on localhost, with the default port (5672), username (guest), password (guest) and virtual host ('/').
147
+
148
+ The next line
149
149
 
150
150
  <pre>
151
151
  <code>
@@ -153,8 +153,8 @@ channel = AMQP::Channel.new(connection)
153
153
  </code>
154
154
  </pre>
155
155
 
156
- opens the channel. AMQP is a multi-channeled protocol. Channels is a way to multiplex a TCP connection.
157
- Because channels are open on a connection, AMQP::Channel constructor takes connection object as a parameter.
156
+ opens a new _channel_. AMQP is a multi-channeled protocol that uses channels to multiplex a TCP connection.
157
+ Channels are opened on a connection, therefore the AMQP::Channel constructor takes a connection object as a parameter.
158
158
 
159
159
  This line
160
160
 
@@ -164,11 +164,11 @@ queue = channel.queue("amqpgem.examples.helloworld", :auto_delete => true)
164
164
  </code>
165
165
  </pre>
166
166
 
167
- declares a queue on the channel we've just opened. Queues are where consumer applications get messages from.
168
- We declare this queue with "auto-delete" parameter. Basically, that means "when there is no one left
169
- consuming messages from this queue, delete it".
167
+ declares a _queue_ on the channel that we have just opened. Consumer applications get messages from queues.
168
+ We declared this queue with the "auto-delete" parameter. Basically, this means that the queue will be deleted when there are no more processes
169
+ consuming messages from it.
170
170
 
171
- The next line,
171
+ The next line
172
172
 
173
173
  <pre>
174
174
  <code>
@@ -176,27 +176,24 @@ exchange = channel.direct("")
176
176
  </code>
177
177
  </pre>
178
178
 
179
- instantiates an exchange. Exchange is where messages are sent by producers. Exchanges route messages to queues
180
- according to rules called bindings. In this particular example, there are no explicitly defined bindings.
181
- Exchange we defined is known as default exchange and it has implied binding to all queues. Before we get
182
- into that, lets see how we define a handler for incoming messages:
179
+ instantiates an _exchange_. Exchanges receive messages that are sent by producers. Exchanges route messages to queues
180
+ according to rules called _bindings_. In this particular example, there are no explicitly defined bindings.
181
+ The exchange that we defined is known as the _default exchange_ and it has implied bindings to all queues. Before we get
182
+ into that, let us see how we define a handler for incoming messages
183
183
 
184
184
  <pre>
185
185
  <code>
186
186
  queue.subscribe do |payload|
187
187
  puts "Received a message: #{payload}. Disconnecting..."
188
-
189
- connection.close {
190
- EM.stop { exit }
191
- }
188
+ connection.close { EventMachine.stop }
192
189
  end
193
190
  </code>
194
191
  </pre>
195
192
 
196
- {AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes
197
- AMQP connection and runs a callback that stops EventMachine reactor.
193
+ {AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes the
194
+ AMQP connection and runs a callback that stops the EventMachine reactor.
198
195
 
199
- Finally, we publish our message:
196
+ Finally, we publish our message
200
197
 
201
198
  <pre>
202
199
  <code>
@@ -204,40 +201,40 @@ exchange.publish "Hello, world!", :routing_key => queue.name
204
201
  </code>
205
202
  </pre>
206
203
 
207
- Routing key is one of _message attributes_. Default exchange will route message to a queue that has the same name
208
- as message's routing key. This is how our message ends up in amqpgem.examples.helloworld queue.
204
+ Routing key is one of the _message attributes_. The default exchange will route the message to a queue that has the same name
205
+ as the message's routing key. This is how our message ends up in the "amqpgem.examples.helloworld" queue.
209
206
 
210
- This first example can be modified to use method chaining technique:
207
+ This first example can be modified to use the method chaining technique:
211
208
 
212
209
  <script src="https://gist.github.com/998691.js"> </script>
213
210
 
214
- (if the example above isn't displayed, see this "gist":https://gist.github.com/998691)
211
+ This diagram demonstrates the "Hello, world" example data flow:
212
+
213
+ !https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/001_hello_world_example_routing.png!
215
214
 
216
- With classes and methods introduced in this example, lets move on to a little bit more
217
- sophisticated one.
215
+ For the sake of simplicity, both the message producer (App I) and the consumer (App II) are running in the same Ruby process.
216
+ Now let us move on to a little bit more sophisticated example.
218
217
 
219
218
 
220
- h2. Blabblr: one-to-many publish/subscribe example
219
+ h2. Blabblr: one-to-many publish/subscribe (pubsub) example
221
220
 
222
- Previous example demonstrated how connection to the broker is made and how to do 1:1 communication
223
- using default exchange. Now lets take a look at another common scenario: broadcast, or multiple consumers
221
+ The previous example demonstrated how a connection to a broker is made and how to do 1:1 communication
222
+ using the default exchange. Now let us take a look at another common scenario: broadcast, or multiple consumers
224
223
  and one producer.
225
224
 
226
- A very well know example of broadcast is Twitter: every time a person tweets, followers receive a notification.
225
+ A very well-known broadcast example is Twitter: every time a person tweets, followers receive a notification.
227
226
  Blabbr, our imaginary information network, models this scenario: every network member has a separate
228
- queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow official NBA
229
- account on Blabbr to get updates about what is up in the world of basketball. Here is the code:
227
+ queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow the official NBA
228
+ account on Blabbr to get updates about what is happening in the world of basketball. Here is the code:
230
229
 
231
230
  <script src="https://gist.github.com/998692.js"> </script>
232
231
 
233
- (if the example above isn't displayed, see this "gist":https://gist.github.com/998692)
234
-
235
- First line has a few difference from "Hello, world" example above:
232
+ The first line has a few differences from the "Hello, world" example above:
236
233
 
237
234
  * We use {AMQP.start} instead of {AMQP.connect}
238
- * Instead of return values, we pass connection method a block and it yields connection
239
- object back as soon as connection is established.
240
- * Instead of passing connection parameters as a hash, we used a URI string.
235
+ * Instead of return values, we pass a block to the connection method and it yields a connection
236
+ object back as soon as the connection is established.
237
+ * Instead of passing connection parameters as a hash, we use a URI string.
241
238
 
242
239
  {AMQP.start} is just a convenient way to do
243
240
 
@@ -251,15 +248,15 @@ end
251
248
  </code>
252
249
  </pre>
253
250
 
254
- {AMQP.start} call blocks current thread so it's use is limited to scripts and small command
251
+ The {AMQP.start} call blocks the current thread which means that its use is limited to scripts and small command
255
252
  line applications. Blabbr is just that.
256
253
 
257
- {AMQP.connect}, when invoked with a block, will yield connection object to it as soon as AMQP connection
258
- is open. Finally, connection parameters maybe given as a Hash or as a connection string. {AMQP.connect}
259
- method documentation has all the details.
254
+ {AMQP.connect}, when invoked with a block, will yield a connection object as soon as the AMQP connection
255
+ is open. Finally, connection parameters may be supplied as a Hash or as a connection string. The {AMQP.connect}
256
+ method documentation contains all of the details.
260
257
 
261
- Opening a channel in this example is no different from opening a channel in the example before that,
262
- but exchange is instantiated differently:
258
+ In this example, opening a channel is no different to opening a channel in the previous example,
259
+ however, the exchange is declared differently
263
260
 
264
261
  <pre>
265
262
  <code>
@@ -267,8 +264,8 @@ exchange = channel.fanout("nba.scores")
267
264
  </code>
268
265
  </pre>
269
266
 
270
- Exchange we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. Fanout exchanges deliver messages to every queue that
271
- was bound to it: exactly what we want in case of Blabbr!
267
+ The exchange that we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. A fanout exchange delivers messages to all of the queues that
268
+ are bound to it: exactly what we want in the case of Blabbr!
272
269
 
273
270
  This piece of code
274
271
 
@@ -280,9 +277,9 @@ end
280
277
  </code>
281
278
  </pre>
282
279
 
283
- is similar to how we subscribed for message delivery before, but what does that {AMQP::Queue#bind}
284
- method do? It sets up a _binding_ between the queue and an exchange you pass to it. We need to do this
285
- to make sure that our fanout exchange routes messages to follower queues.
280
+ is similar to the subscription code that we used for message delivery previously, but what does that {AMQP::Queue#bind}
281
+ method do? It sets up a binding between the queue and the exchange that you pass to it. We need to do this
282
+ to make sure that our fanout exchange routes messages to the queues of any subscribed followers.
286
283
 
287
284
  <pre>
288
285
  <code>
@@ -290,9 +287,13 @@ exchange.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88")
290
287
  </code>
291
288
  </pre>
292
289
 
293
- demonstrates {AMQP::Exchange#publish} calls chaining. Because Blabbr members use fanout exchange
294
- for publishing, there is no need to specify routing key: every queue that was bound to exchange receiving
295
- a message will get it's own message copy, regardless of queue name and routing key used.
290
+ demonstrates {AMQP::Exchange#publish} call chaining. Blabbr members use a fanout exchange
291
+ for publishing, so there is no need to specify a message routing key because every queue that is bound to the exchange will get its own copy of all messages, regardless of the queue name and routing key used.
292
+
293
+ A diagram for Blabbr looks like this:
294
+
295
+ !https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/002_blabbr_example_routing.png!
296
+
296
297
 
297
298
  Next we use EventMachine's {http://eventmachine.rubyforge.org/EventMachine.html#M000466 add_timer} method to
298
299
  run a piece of code in 1 second from now:
@@ -302,44 +303,40 @@ run a piece of code in 1 second from now:
302
303
  EventMachine.add_timer(1) do
303
304
  exchange.delete
304
305
 
305
- connection.close {
306
- EM.stop { exit }
307
- }
306
+ connection.close { EventMachine.stop }
308
307
  end
309
308
  </code>
310
309
  </pre>
311
310
 
312
- The code we want to run deletes exchange we declared earlier using {AMQP::Exchange#delete} and closes AMQP
313
- connection with {AMQP::Session#close}. Finally, we stop EventMachine event loop and exit.
311
+ The code that we want to run deletes the exchange that we declared earlier using {AMQP::Exchange#delete} and closes the AMQP
312
+ connection with {AMQP::Session#close}. Finally, we stop the EventMachine event loop and exit.
314
313
 
315
- Blabbr is pretty unlikely to secure hundreds of millions in funding but it does a pretty good job of
314
+ Blabbr is pretty unlikely to secure hundreds of millions of dollars in funding, but it does a pretty good job of
316
315
  demonstrating how one can use AMQP fanout exchanges to do broadcasting.
317
316
 
318
317
 
319
318
 
320
319
  h2. Weathr: many-to-many topic routing example
321
320
 
322
- So far we have seen point-to-point communication and broadcast. These two are possible with many protocols:
323
- HTTP handles these scenarios just fine. What differentiates AMQP? Next we are going to introduce you to topic
324
- exchanges and routing with patterns, one of the features that makes AMQP very powerful.
321
+ So far, we have seen point-to-point communication and broadcasting. Those two communication styles are possible with many protocols, for instance,
322
+ HTTP handles these scenarios just fine. You may ask "what differentiates AMQP?" Well, next we are going to introduce you to _topic
323
+ exchanges_ and routing with patterns, one of the features that makes AMQP very powerful.
325
324
 
326
- Our third example is weather condition updates. What makes it different from the previous two is that
327
- not all consumers are interested in all messages: people who live in Portland usually don't care about
328
- weather in Hong Kong very much (unless they are going there soon). They are certainly interested in
325
+ Our third example involves weather condition updates. What makes it different from the previous two examples is that
326
+ not all of the consumers are interested in all of the messages. People who live in Portland usually do not care about
327
+ the weather in Hong Kong (unless they are visiting soon). They are much more interested in
329
328
  weather conditions around Portland, possibly all of Oregon and sometimes a few neighbouring states.
330
329
 
331
330
  Our example features multiple consumer applications monitoring updates for different regions. Some are
332
- interested in updates for a specific city, others for a specific state and so on all the way up to continents.
333
- Updates may overlap: an update for San Diego, CA _is_ an update for California, and should certainly show up
334
- on North America updates list.
331
+ interested in updates for a specific city, others for a specific state and so on, all the way up to continents.
332
+ Updates may overlap so that an update for San Diego, CA appears as an update for California, but also should show up
333
+ on the North America updates list.
335
334
 
336
335
  Here is the code:
337
336
 
338
337
  <script src="https://gist.github.com/998694.js"> </script>
339
338
 
340
- (if the example above isn't displayed, see this "gist":https://gist.github.com/998694)
341
-
342
- First line that is different from Blabbr example is
339
+ The first line that is different from the Blabbr example is
343
340
 
344
341
  <pre>
345
342
  <code>
@@ -347,9 +344,9 @@ exchange = channel.topic("pub/sub", :auto_delete => true)
347
344
  </code>
348
345
  </pre>
349
346
 
350
- We use a _topic exchange_ here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
351
- where consumers indicate what topics they are interested in (think of it as of subscribing to a feed for individual tag
352
- of your favourite blog as opposed to full feed). They do it by specifying _routing pattern_ on binding, for example:
347
+ We use a topic exchange here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
348
+ where consumers indicate which topics they are interested in (think of it as subscribing to a feed for an individual tag
349
+ in your favourite blog as opposed to the full feed). Routing with a topic exchange is done by specifying a _routing pattern_ on binding, for example:
353
350
 
354
351
  <pre>
355
352
  <code>
@@ -359,23 +356,25 @@ end
359
356
  </code>
360
357
  </pre>
361
358
 
362
- Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using {AMQP::Queue#bind} method.
363
- This means that only messages with routing key matching americas.south.# will be routed to that queue. Routing pattern consists of several words
364
- separated by dots, similarly to URI path segments joined by slash. A few of examples:
359
+ Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using the {AMQP::Queue#bind} method.
360
+ This means that only messages with a routing key matching "americas.south.#" will be routed to that queue. A routing pattern consists of several words
361
+ separated by dots, in a similar way to URI path segments joined by slashes. Here are a few examples:
365
362
 
366
363
  * asia.southeast.thailand.bangkok
367
364
  * sports.basketball
368
365
  * usa.nasdaq.aapl
369
366
  * tasks.search.indexing.accounts
370
367
 
371
- Now lets take a look at a few routing keys that do match "americas.south.#" pattern:
368
+ Now let us take a look at a few routing keys that match the "americas.south.#" pattern:
372
369
 
373
370
  * americas.south
374
371
  * americas.south.*brazil*
375
372
  * americas.south.*brazil.saopaolo*
376
373
  * americas.south.*chile.santiago*
377
374
 
378
- In other words, # part of the pattern matches 0 or more words. For "americas.south.*", some of matching routing keys are
375
+ In other words, the "#" part of the pattern matches 0 or more words.
376
+
377
+ For a pattern like "americas.south.*", some matching routing keys would be:
379
378
 
380
379
  * americas.south.*brazil*
381
380
  * americas.south.*chile*
@@ -386,24 +385,29 @@ but not
386
385
  * americas.south
387
386
  * americas.south.chile.santiago
388
387
 
389
- 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
388
+ so "*" only matches a single word. The AMQP v0.9.1 specification says that topic segments (words) may contain the letters A-Z and a-z
390
389
  and digits 0-9.
391
390
 
391
+ A (very simplistic) diagram to demonstrate topic exchange in action:
392
+
393
+ !https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/003_weathr_example_routing.png!
394
+
395
+
392
396
  One more thing that is different from previous examples is that the block we pass to {AMQP::Queue#subscribe} now takes two arguments:
393
- header and body (aka payload). Long story short, the _header_ parameter lets you access metadata associated with the message. Some
394
- examples of message metadata attributes are
397
+ a _header_ and a _body_ (often called the _payload_). Long story short, the header parameter lets you access metadata associated with the message. Some
398
+ examples of message metadata attributes are:
395
399
 
396
400
  * message content type
397
401
  * message content encoding
398
402
  * message priority
399
403
  * message expiration time
400
404
  * message identifier
401
- * reply to, to what message this message is a reply to
402
- * application id, identifier of application that produced the message
405
+ * reply to (specifies which message this is a reply to)
406
+ * application id (identifier of the application that produced the message)
403
407
 
404
408
  and so on.
405
409
 
406
- As this binding demonstrates, # (and *) can appear in the beginning of routing patterns, too:
410
+ As the following binding demonstrates, "#" and "*" can also appear at the beginning of routing patterns:
407
411
 
408
412
  <pre>
409
413
  <code>
@@ -413,9 +417,9 @@ end
413
417
  </code>
414
418
  </pre>
415
419
 
416
- Publishing of messages is not different from previous examples. Running this example demonstrates that, for example,
417
- message published with routing key of "americas.north.us.ca.berkeley" is routed to several queues: us.california and
418
- _server-named queue_ we declared by passing blank string as the name:
420
+ For this example the publishing of messages is no different from that of previous examples. If we were to run the program,
421
+ a message published with a routing key of "americas.north.us.ca.berkeley" would be routed to 2 queues: "us.california" and the
422
+ _server-named queue_ that we declared by passing a blank string as the name:
419
423
 
420
424
  <pre>
421
425
  <code>
@@ -427,49 +431,180 @@ end
427
431
  </code>
428
432
  </pre>
429
433
 
430
- Name of server-named queue is generated by the broker and sent back to the client with queue declaration confirmation.
431
- Because of queue name is not known before reply arrives, we passed {AMQP::Channel#queue} a callback and it yielded us back
434
+ The name of the server-named queue is generated by the broker and sent back to the client with a queue declaration confirmation.
435
+ Because the queue name is not known before the reply arrives, we pass {AMQP::Channel#queue} a callback and it yields us back
432
436
  a queue object once confirmation has arrived.
433
437
 
434
438
 
435
439
  h3. Avoid race conditions
436
440
 
437
441
  A word of warning: you may find examples on the Web of {AMQP::Channel#queue} usage that do not use
438
- callback: we *strongly recommend you always use a callback for server-named queues*. Otherwise your code may be a subject
439
- 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
440
- 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
442
+ callbacks. We *recommend that you use a callback for server-named queues*, otherwise your code may be subject
443
+ to "race conditions":http://en.wikipedia.org/wiki/Race_condition. Even though the amqp gem tries to be reasonably smart and protect you from most common problems
444
+ (for example, binding operations will be delayed until after queue name is received from the broker), there
445
+ is no way it can do so for every case. The primary reason for supporting {AMQP::Channel#queue} usage without a callback for server-named queues is
441
446
  backwards compatibility with earlier versions.
442
447
 
443
448
 
449
+
450
+ h2. Integration with objects
451
+
452
+ Since Ruby is a genuine object-oriented language, it is important to demonstrate how the Ruby amqp gem can be integrated
453
+ into rich object-oriented code.
454
+
455
+ The {AMQP::Queue#subscribe} callback does not have to be a block. It can be any Ruby object that responds to `call`.
456
+ A common technique is to combine {http://rubydoc.info/stdlib/core/1.8.7/Object:method Object#method} and {http://rubydoc.info/stdlib/core/1.8.7/Method:to_proc Method#to_proc}
457
+ and use object methods as message handlers.
458
+
459
+ An example to demonstrate this technique:
460
+
461
+ <pre>
462
+ <code>
463
+ class Consumer
464
+
465
+ #
466
+ # API
467
+ #
468
+
469
+ def handle_message(metadata, payload)
470
+ puts "Received a message: #{payload}, content_type = #{metadata.content_type}"
471
+ end # handle_message(metadata, payload)
472
+ end
473
+
474
+
475
+ class Worker
476
+
477
+ #
478
+ # API
479
+ #
480
+
481
+
482
+ def initialize(channel, queue_name = AMQ::Protocol::EMPTY_STRING, consumer = Consumer.new)
483
+ @queue_name = queue_name
484
+
485
+ @channel = channel
486
+ @channel.on_error(&method(:handle_channel_exception))
487
+
488
+ @consumer = consumer
489
+ end # initialize
490
+
491
+ def start
492
+ @queue = @channel.queue(@queue_name, :exclusive => true)
493
+ @queue.subscribe(&@consumer.method(:handle_message))
494
+ end # start
495
+
496
+
497
+
498
+ #
499
+ # Implementation
500
+ #
501
+
502
+ def handle_channel_exception(channel, channel_close)
503
+ puts "Oops... a channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
504
+ end # handle_channel_exception(channel, channel_close)
505
+ end
506
+ </code>
507
+ </pre>
508
+
509
+ The "Hello, world" example can be ported to use this technique:
510
+ <script src="https://gist.github.com/1009447.js"> </script>
511
+
512
+ The most important line in this example is
513
+
514
+ <pre>
515
+ <code>
516
+ @queue.subscribe(&@consumer.method(:handle_message))
517
+ </code>
518
+ </pre>
519
+
520
+ Ampersand (&) preceding an object is equivalent to calling the #to_proc method on it. We obtain a Consumer#handle_message method reference
521
+ with
522
+
523
+ <pre>
524
+ <code>
525
+ @consumer.method(:handle_message)
526
+ </code>
527
+ </pre>
528
+
529
+ and then the ampersand calls #to_proc on it. {AMQP::Queue#subscribe} then will be using this Proc instance to handle incoming messages.
530
+
531
+
532
+ Note that the `Consumer` class above can be easily tested in isolation, without spinning up any AMQP connections.
533
+ Here is one example using "RSpec":http://relishapp.com/rspec
534
+
535
+ <pre>
536
+ <code>
537
+ require "ostruct"
538
+ require "json"
539
+
540
+ # RSpec example
541
+ describe Consumer do
542
+ describe "when a new message arrives" do
543
+ subject { described_class.new }
544
+
545
+ let(:metadata) do
546
+ o = OpenStruct.new
547
+
548
+ o.content_type = "application/json"
549
+ o
550
+ end
551
+ let(:payload) { JSON.encode({ :command => "reload_config" }) }
552
+
553
+ it "does some useful work" do
554
+ # check preconditions here if necessary
555
+
556
+ subject.handle_message(metadata, payload)
557
+
558
+ # add your code expectations here
559
+ end
560
+ end
561
+ end
562
+ </code>
563
+ </pre>
564
+
565
+
444
566
  h2. Wrapping up
445
567
 
446
- This tutorial ends here. Congratulations! You have learned quite a bit about both AMQP 0.9.1 and amqp gem.
568
+ This is the end of the tutorial. Congratulations! You have learned quite a bit about both AMQP v0.9.1 and the amqp gem. This is only
569
+ the tip of the iceberg. AMQP has many more features built into the protocol:
570
+
571
+ * Reliable delivery of messages
572
+ * Message confirmations (a way to tell broker that a message was or was not processed successfully)
573
+ * Message redelivery when consumer applications fail or crash
574
+ * Load balancing of messages between multiple consumers
575
+ * Message metadata attributes
576
+
577
+ and so on. Other guides explain these features in depth, as well as use cases for them. To stay up to date with amqp gem
578
+ development, "follow @rubyamqp on Twitter":http://twitter.com/rubyamqp and "join our mailing list":http://groups.google.com/group/ruby-amqp.
579
+
580
+
447
581
 
448
582
 
449
583
  h2. What to read next
450
584
 
451
585
  Documentation is organized as a number of {file:docs/DocumentationGuidesIndex.textile documentation guides}, covering all kinds of
452
- topics from {file:docs/Routing.textile routing} to {file:docs/ErrorHandling.textile error handling} to
586
+ topics from {file:docs/Exchanges.textile use cases for various exchange types} to {file:docs/ErrorHandling.textile error handling} and
453
587
  {file:docs/VendorSpecificExchanges.textile Broker-specific AMQP 0.9.1 extensions}.
454
588
 
455
- To learn more on what you have seen in this tutorial, check out
589
+ We recommend that you read the following guides next, if possible, in this order:
456
590
 
457
- * {file:docs/ConnectingToTheBroker.textile Connection to the broker}
458
- * {file:docs/Queues.textile Queues}
459
- * {file:docs/Exchanges.textile Exchanges}
460
- * {file:docs/Bindings.textile Bindings}
591
+ * {file:docs/ConnectingToTheBroker.textile Connection to the broker}. This guide explains how to connect to an AMQP broker and how to integrate the amqp gem into standalone and Web applications.
592
+ * {file:docs/Queues.textile Working With Queues}. This guide focuses on features that consumer applications use heavily.
593
+ * {file:docs/Exchanges.textile Working With Exchanges}. This guide focuses on features that producer applications use heavily.
594
+ * {file:docs/PatternsAndUseCases.textile Patterns & Use Cases}. This guide focuses implementation of "common messaging patterns":http://www.eaipatterns.com/ using AMQP Model features as building blocks.
595
+ * {file:docs/ErrorHandling.textile Error Handling & Recovery}. This guide explains how to handle protocol errors, network failures and other things that may go wrong in real world projects.
461
596
 
462
- 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
597
+ If you are migrating your application from earlier versions of the amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is the
463
598
  {file:docs/08Migration.textile amqp gem 0.8 migration guide}.
464
599
 
465
600
 
466
601
  h2. Tell us what you think!
467
602
 
468
- 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:
469
- what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
470
- key to making documentation better.
603
+ Please take a moment to tell us what you think about this guide "on Twitter":http://twitter.com/rubyamqp or the "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp.
604
+ Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is
605
+ key to making the documentation better.
471
606
 
472
- 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
607
+ If, for some reason, you cannot use the communication channels mentioned above, you can "contact the author of the guides directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
473
608
 
474
609
 
475
610
  <div id="disqus_thread"></div>