amqp 0.8.4 → 0.9.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -1
- data/README.md +8 -4
- data/docs/08Migration.textile +7 -1
- data/docs/AMQP091ModelExplained.textile +5 -1
- data/docs/DocumentationGuidesIndex.textile +10 -2
- data/docs/ErrorHandling.textile +5 -0
- data/docs/Exchanges.textile +1 -1
- data/docs/GettingStarted.textile +4 -4
- data/docs/PatternsAndUseCases.textile +49 -132
- data/docs/Queues.textile +2 -0
- data/docs/RabbitMQVersions.textile +14 -0
- data/docs/TestingWithEventedSpec.textile +70 -26
- data/docs/VendorSpecificExtensions.textile +4 -0
- data/docs/diagrams/006_amqp_091_message_acknowledgements.png +0 -0
- data/docs/diagrams/007_rabbitmq_publisher_confirms.png +0 -0
- data/examples/error_handling/automatically_recovering_hello_world_consumer.rb +1 -1
- data/examples/error_handling/connection_loss_handler.rb +1 -1
- data/examples/error_handling/hello_world_producer.rb +17 -5
- data/examples/error_handling/reopening_a_channel_after_channel_level_exception.rb +67 -0
- data/examples/patterns/command/producer.rb +1 -1
- data/examples/patterns/event/consumer.rb +43 -0
- data/examples/patterns/event/producer.rb +60 -0
- data/examples/queues/cancel_default_consumer.rb +40 -0
- data/lib/amqp/channel.rb +2 -2
- data/lib/amqp/exchange.rb +2 -2
- data/lib/amqp/queue.rb +1 -1
- data/lib/amqp/version.rb +1 -1
- data/spec/integration/exchange_declaration_spec.rb +69 -0
- data/spec/integration/regressions/concurrent_publishing_on_the_same_channel_spec.rb +1 -1
- metadata +20 -10
data/CHANGELOG
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Version 0.
|
1
|
+
= Version 0.9.0 (not yet released)
|
2
2
|
|
3
3
|
|
4
4
|
= Version 0.8.1
|
@@ -6,6 +6,7 @@
|
|
6
6
|
* [BUG] AMQP::Queue#status can no longer result in queue instance @ivars being changed.
|
7
7
|
* [API] AMQP::Channel#reuse allows channel instance to be reused with a different channel id, may be used to recover from channel-level exceptions.
|
8
8
|
|
9
|
+
|
9
10
|
= Version 0.8.0
|
10
11
|
|
11
12
|
* [API] AMQP::Session#on_skipped_heartbeats callback that can be used to handle skipped heartbeats (for cases when TCP network failure detection is not timely enough)
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# About Ruby amqp gem #
|
2
2
|
|
3
|
-
Ruby amqp gem is a widely used, feature-rich, well-maintained asynchronous AMQP 0.9.1 client with batteries included.
|
3
|
+
[Ruby amqp gem](http://rubyamqp.info) is a widely used, feature-rich, well-maintained asynchronous AMQP 0.9.1 client with batteries included.
|
4
4
|
This library works with Ruby 1.8.7 (*except for p249*, see the FAQ), Ruby 1.9.2, Ruby 1.9.3, [JRuby](http://jruby.org), [Rubinius](http://rubini.us) as well as [REE](http://www.rubyenterpriseedition.com), and is licensed under the [Ruby License](http://www.ruby-lang.org/en/LICENSE.txt)
|
5
5
|
|
6
|
-
0.8.0
|
6
|
+
0.8.0 and later versions of amqp gem implement [AMQP 0.9.1](http://bit.ly/amqp-model-explained) (see also [AMQP 0.9.1 spec document](http://bit.ly/amqp091spec)) and support [RabbitMQ extensions to AMQP 0.9.1](http://www.rabbitmq.com/extensions.html).
|
7
7
|
|
8
8
|
[](http://travis-ci.org/ruby-amqp/amqp)
|
9
9
|
|
@@ -123,7 +123,7 @@ to learn more about AMQP principles & concepts.
|
|
123
123
|
|
124
124
|
We believe that in order to be a library our users **really** love, we need to care about documentation as much as (or more)
|
125
125
|
code readability, API beauty and autotomated testing across 5 Ruby implementations on multiple operating systems. We do care
|
126
|
-
about our [documentation](http://
|
126
|
+
about our [documentation](http://rubyamqp.info): **if you don't find your answer in documentation, we consider it a high severity bug** that you
|
127
127
|
should [file to us](http://github.com/ruby-amqp/amqp/issues). Or just complain to [@rubyamqp](https://twitter.com/rubyamqp) on Twitter.
|
128
128
|
|
129
129
|
|
@@ -150,7 +150,7 @@ There is also a work-in-progress [Messaging Patterns and Use Cases With AMQP](ht
|
|
150
150
|
|
151
151
|
### Documentation guides ###
|
152
152
|
|
153
|
-
[Documentation guides](http://bit.ly/amqp-gem-docs) describe the library itself as well as AMQP concepts, usage scenarios, topics like working with exchanges and queues,
|
153
|
+
[Documentation guides](http://rubyamqp.info) (also [on rubydoc.info](http://bit.ly/amqp-gem-docs)) describe the library itself as well as AMQP concepts, usage scenarios, topics like working with exchanges and queues,
|
154
154
|
error handing & recovery, broker-specific extensions, TLS support, troubleshooting and so on. Most of the documentation is in these guides.
|
155
155
|
|
156
156
|
|
@@ -172,6 +172,10 @@ Upgrading from amqp gem 0.6.x and 0.7.x to to 0.8.0.RCs is straightforward, plea
|
|
172
172
|
The same guide explains amqp gem versions history and why you would want to upgrade.
|
173
173
|
|
174
174
|
|
175
|
+
## Maintainer Information
|
176
|
+
|
177
|
+
amqp gem is maintained by [Michael Klishin](https://github.com/michaelklishin).
|
178
|
+
|
175
179
|
|
176
180
|
## Community
|
177
181
|
|
data/docs/08Migration.textile
CHANGED
@@ -76,7 +76,13 @@ h2. Why developers should upgrade to 0.8.0
|
|
76
76
|
|
77
77
|
h2. AMQP protocol version change
|
78
78
|
|
79
|
-
amqp gem before 0.8.0.
|
79
|
+
amqp gem before 0.8.0 (0.6.x, 0.7.x) series implemented (most of) AMQP 0.8 specification. amqp gem 0.8.0 implements AMQP 0.9.1 and thus
|
80
|
+
*requires RabbitMQ version 2.0 or later*. See {file:docs/RabbitMQVersions.textile RabbitMQ versions} for more information about
|
81
|
+
RabbitMQ versions support and how obtain up-todate packages for your operating system.
|
82
|
+
|
83
|
+
<span class="note">
|
84
|
+
amqp gem 0.8.0 and later versions implement AMQP 0.9.1 and thus *requires RabbitMQ version 2.0 or later*
|
85
|
+
</span>
|
80
86
|
|
81
87
|
|
82
88
|
|
@@ -262,7 +262,11 @@ To make it possible for a single broker to host multiple isolated "environments"
|
|
262
262
|
of _virtual hosts_ (vhosts). They are similar to virtual hosts used by many popular Web servers and provide completely isolated environments
|
263
263
|
in which AMQP entities live. AMQP clients specify what vhosts they want to use during AMQP connection negotiation.
|
264
264
|
|
265
|
-
An AMQP 0.9.1 vhost name can be any non-blank string.
|
265
|
+
An AMQP 0.9.1 vhost name can be any non-blank string. Some most common use cases for vhosts are
|
266
|
+
|
267
|
+
* To separate AMQP entities used by different groups of applications
|
268
|
+
* To separate multiple installations/environments (e.g. production, staging) of one or more applications
|
269
|
+
* To implement a multi-tenant environment
|
266
270
|
|
267
271
|
|
268
272
|
h2. AMQP is Extensible
|
@@ -20,7 +20,7 @@ but *most of the content is concentrated in just 3-4 guides* that are about 80%
|
|
20
20
|
Here is a summary of guides and their content:
|
21
21
|
|
22
22
|
<dl>
|
23
|
-
<dt>{file:docs/GettingStarted.textile Getting Started}</dt>
|
23
|
+
<dt>{file:docs/GettingStarted.textile Getting Started with Ruby amqp gem and RabbitMQ}</dt>
|
24
24
|
<dd>
|
25
25
|
Walks you through gem installation and 3 applications that demonstrate what AMQP has to offer. Explains how amqp gem should
|
26
26
|
be integrated into rich object-oriented Ruby programs.
|
@@ -73,6 +73,13 @@ Here is a summary of guides and their content:
|
|
73
73
|
recovery is hard. How to survive typical problems. What other tools can help (e.g. HAProxy).
|
74
74
|
</dd>
|
75
75
|
|
76
|
+
<dt>{file:docs/TestingWithEventedSpec.textile Unit and integration testing of AMQP applications}</dt>
|
77
|
+
<dd>
|
78
|
+
Unit testing of asynchronous code: typical problems and ways to solve them. An oviewview of evented-spec, the gem that amqp gem itself uses
|
79
|
+
for "its test suite":https://github.com/ruby-amqp/amqp/tree/master/spec.
|
80
|
+
</dd>
|
81
|
+
|
82
|
+
|
76
83
|
<dt>{file:docs/RabbitMQVersions.textile RabbitMQ versions}</dt>
|
77
84
|
<dd>
|
78
85
|
RabbitMQ versions that amqp gem supports. Popular Linux distributions and RabbitMQ versions they ship. How to obtain up-to-date official
|
@@ -89,7 +96,7 @@ When more than one guide describes the same concept, we make sure to use cross-r
|
|
89
96
|
|
90
97
|
h2. Full guide list
|
91
98
|
|
92
|
-
* {file:docs/GettingStarted.textile Getting Started}
|
99
|
+
* {file:docs/GettingStarted.textile Getting Started with Ruby amqp gem and RabbitMQ}
|
93
100
|
* {file:docs/AMQP091ModelExplained.textile AMQP 0.9.1 Model Explained}
|
94
101
|
* {file:docs/ConnectingToTheBroker.textile Connecting to the Broker}
|
95
102
|
* {file:docs/Queues.textile Working With Queues}
|
@@ -99,6 +106,7 @@ h2. Full guide list
|
|
99
106
|
* {file:docs/Durability.textile Durability and Message Persistence}
|
100
107
|
* {file:docs/ErrorHandling.textile Error Handling and Recovery}
|
101
108
|
* {file:docs/08Migration.textile Upgrading from version 0.6.x/0.7.x to 0.8.x and above}
|
109
|
+
* {file:docs/TestingWithEventedSpec.textile Unit and integration testing of AMQP applications}
|
102
110
|
* {file:docs/Troubleshooting.textile Troubleshooting and debugging AMQP applications}
|
103
111
|
* {file:docs/Clustering.textile Clustering}
|
104
112
|
* {file:docs/RabbitMQVersions.textile RabbitMQ versions}
|
data/docs/ErrorHandling.textile
CHANGED
@@ -502,6 +502,11 @@ Error handling can be easily integrated into object-oriented Ruby code (in fact,
|
|
502
502
|
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}
|
503
503
|
and use object methods as error handlers. For example of this, see section on connection-level exceptions above.
|
504
504
|
|
505
|
+
<span class="note">
|
506
|
+
Because channel-level exceptions may be raised because of multiple unrelated reasons and often indicate misconfigurations, how they are handled is
|
507
|
+
very specific to particular applications. A common strategy is to log an error and then open and use another channel.
|
508
|
+
</span>
|
509
|
+
|
505
510
|
|
506
511
|
h3. Common channel-level exceptions and what they mean
|
507
512
|
|
data/docs/Exchanges.textile
CHANGED
@@ -226,7 +226,7 @@ exchange = channel.direct("nodes.metadata")
|
|
226
226
|
</code>
|
227
227
|
</pre>
|
228
228
|
|
229
|
-
Both methods asynchronously declare
|
229
|
+
Both methods asynchronously declare an exchange named "nodes.metadata". Because the declaration necessitates a network round trip, publishing
|
230
230
|
operations on {AMQP::Exchange} instances are delayed until a broker reply (`exchange.declare-ok`) is received.
|
231
231
|
|
232
232
|
Also, both methods let you pass a block to run a piece of code when the broker responds with `exchange.declare-ok`
|
data/docs/GettingStarted.textile
CHANGED
@@ -47,7 +47,7 @@ On Debian and Ubuntu, you can either "download the RabbitMQ .deb package":http:/
|
|
47
47
|
For RPM-based distributions like RedHat or CentOS, the RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
|
48
48
|
|
49
49
|
<span class="note">
|
50
|
-
The RabbitMQ package that ships with recent Ubuntu 10.10
|
50
|
+
The RabbitMQ package that ships with recent Ubuntu versions (for example, 10.10) 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).
|
51
51
|
</span>
|
52
52
|
|
53
53
|
|
@@ -58,7 +58,7 @@ h3. Make sure that you have Ruby and "Rubygems":http://docs.rubygems.org/read/ch
|
|
58
58
|
|
59
59
|
This guide assumes that you have installed one of the following supported Ruby implementations:
|
60
60
|
|
61
|
-
* Ruby v1.8.7
|
61
|
+
* Ruby v1.8.7 [except for 1.8.7-p248 and -p249 that have "a bug that severely affects amqp gem":http://bit.ly/iONBmH]
|
62
62
|
* Ruby v1.9.2
|
63
63
|
* Ruby v1.9.3
|
64
64
|
* JRuby (we recommend v1.6)
|
@@ -86,7 +86,7 @@ h3. You can also use Bundler to install the gem
|
|
86
86
|
<code>
|
87
87
|
source :rubygems
|
88
88
|
|
89
|
-
gem "amqp", "~> 0.8.
|
89
|
+
gem "amqp", "~> 0.8.4" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "0.8.x-stable"
|
90
90
|
</code>
|
91
91
|
</pre>
|
92
92
|
|
@@ -101,7 +101,7 @@ irb -rubygems
|
|
101
101
|
:001 > require "amqp"
|
102
102
|
=> true
|
103
103
|
:002 > AMQP::VERSION
|
104
|
-
=> "0.8.
|
104
|
+
=> "0.8.4"
|
105
105
|
</code>
|
106
106
|
</pre>
|
107
107
|
|
@@ -33,7 +33,8 @@ There are other, more specialized group of messaging patterns that are out of sc
|
|
33
33
|
This guide demonstrates implementation of several common routing patterns plus explains how built-in AMQP 0.9.1 features
|
34
34
|
can be used to implement message construction and message transformation patterns.
|
35
35
|
|
36
|
-
|
36
|
+
Note that guide is a work in progress. There are many messaging patterns and new variations are being discovered every year.
|
37
|
+
This guide thus strives to be useful to the 80% of developers instead of being "complete".
|
37
38
|
|
38
39
|
|
39
40
|
|
@@ -86,63 +87,12 @@ h3. Code example
|
|
86
87
|
|
87
88
|
h4. Client code
|
88
89
|
|
89
|
-
<
|
90
|
-
<code>
|
91
|
-
require "amqp"
|
92
|
-
|
93
|
-
EventMachine.run do
|
94
|
-
connection = AMQP.connect
|
95
|
-
channel = AMQP::Channel.new(connection)
|
96
|
-
|
97
|
-
replies_queue = channel.queue("", :exclusive => true, :auto_delete => true)
|
98
|
-
replies_queue.subscribe do |metadata, payload|
|
99
|
-
puts "[response] Response for #{metadata.correlation_id}: #{payload.inspect}"
|
100
|
-
end
|
101
|
-
|
102
|
-
# request time from a peer every 3 seconds
|
103
|
-
EventMachine.add_periodic_timer(3.0) do
|
104
|
-
puts "[request] Sending a request..."
|
105
|
-
channel.default_exchange.publish("get.time",
|
106
|
-
:routing_key => "amqpgem.examples.services.time",
|
107
|
-
:message_id => Kernel.rand(10101010).to_s,
|
108
|
-
:reply_to => replies_queue.name,
|
109
|
-
:immediate => true)
|
110
|
-
end
|
111
|
-
|
112
|
-
|
113
|
-
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
114
|
-
end
|
115
|
-
</code>
|
116
|
-
</pre>
|
90
|
+
<script src="https://gist.github.com/1207763.js"> </script>
|
117
91
|
|
118
92
|
|
119
93
|
h4. Server code
|
120
94
|
|
121
|
-
<
|
122
|
-
<code>
|
123
|
-
require "amqp"
|
124
|
-
|
125
|
-
EventMachine.run do
|
126
|
-
connection = AMQP.connect
|
127
|
-
channel = AMQP::Channel.new(connection)
|
128
|
-
|
129
|
-
requests_queue = channel.queue("amqpgem.examples.services.time", :exclusive => true, :auto_delete => true)
|
130
|
-
requests_queue.subscribe(:ack => true) do |metadata, payload|
|
131
|
-
puts "[requests] Got a request #{metadata.message_id}. Sending a reply..."
|
132
|
-
channel.default_exchange.publish(Time.now.to_s,
|
133
|
-
:routing_key => metadata.reply_to,
|
134
|
-
:correlation_id => metadata.message_id,
|
135
|
-
:immediate => true,
|
136
|
-
:mandatory => true)
|
137
|
-
|
138
|
-
metadata.ack
|
139
|
-
end
|
140
|
-
|
141
|
-
|
142
|
-
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
143
|
-
end
|
144
|
-
</code>
|
145
|
-
</pre>
|
95
|
+
<script src="https://gist.github.com/1207764.js"> </script>
|
146
96
|
|
147
97
|
In the examples above messages are published with the :immediate attribute set. This is not necessary in all
|
148
98
|
cases: sometimes it is OK for requests to sit in the queue without active consumers. Replies, on the other hand,
|
@@ -153,8 +103,13 @@ server application will log returned messages. More on this in the {file:docs/Ex
|
|
153
103
|
|
154
104
|
h3. Related patterns
|
155
105
|
|
156
|
-
|
157
|
-
|
106
|
+
Request/Reply demonstrates two common techniques that are sometimes referred to as messaging patterns of its own:
|
107
|
+
|
108
|
+
* "Correlation Identifier":http://www.eaipatterns.com/CorrelationIdentifier.html (for identifying what request incoming response is for)
|
109
|
+
* "Return Address":http://www.eaipatterns.com/ReturnAddress.html (for identifying where replies should be sent)
|
110
|
+
|
111
|
+
Other related patterns are
|
112
|
+
|
158
113
|
* Scatter/Gather
|
159
114
|
* Smart Proxy
|
160
115
|
|
@@ -187,86 +142,19 @@ h4. Request message attributes
|
|
187
142
|
|
188
143
|
<dl>
|
189
144
|
<dt>:type</dt>
|
190
|
-
<dd>Message type
|
145
|
+
<dd>Message type as a string. For example: gems.install or commands.shutdown</dd>
|
191
146
|
</dl>
|
192
147
|
|
193
148
|
h3. Code example
|
194
149
|
|
195
150
|
h4. Producer (Sender)
|
196
151
|
|
197
|
-
<
|
198
|
-
<code>
|
199
|
-
require "rubygems"
|
200
|
-
require "amqp"
|
201
|
-
require "yaml"
|
202
|
-
|
203
|
-
t = Thread.new { EventMachine.run }
|
204
|
-
sleep(0.5)
|
205
|
-
|
206
|
-
|
207
|
-
connection = AMQP.connect
|
208
|
-
channel = AMQP::Channel.new(connection, :auto_recovery => true)
|
209
|
-
|
210
|
-
channel.prefetch(1)
|
211
|
-
|
212
|
-
# Acknowledgements are good for letting the server know
|
213
|
-
# that the task is finished. If the consumer doesn't send
|
214
|
-
# the acknowledgement, then the task is considered to be unfinished
|
215
|
-
# and will be requeued when consumer closes AMQP connection (because of a crash, for example).
|
216
|
-
channel.queue("amqpgem.examples.patterns.command", :durable => true, :auto_delete => false).subscribe(:ack => true) do |metadata, payload|
|
217
|
-
case metadata.type
|
218
|
-
when "gems.install"
|
219
|
-
data = YAML.load(payload)
|
220
|
-
puts "[gems.install] Received a 'gems.install' request with #{data.inspect}"
|
221
|
-
|
222
|
-
# just to demonstrate a realistic example
|
223
|
-
shellout = "gem install #{data[:gem]} --version '#{data[:version]}'"
|
224
|
-
puts "[gems.install] Executing #{shellout}"; system(shellout)
|
225
|
-
puts "[gems.install] Done"
|
226
|
-
puts
|
227
|
-
else
|
228
|
-
puts "[commands] Unknown command: #{metadata.type}"
|
229
|
-
end
|
230
|
-
|
231
|
-
# message is processed, acknowledge it so that broker discards it
|
232
|
-
metadata.ack
|
233
|
-
end
|
234
|
-
|
235
|
-
puts "[boot] Ready. Will be publishing commands every 10 seconds."
|
236
|
-
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
237
|
-
t.join
|
238
|
-
</code>
|
239
|
-
</pre>
|
152
|
+
<script src="https://gist.github.com/1207758.js"> </script>
|
240
153
|
|
241
154
|
|
242
155
|
h4. Consumer (Recipient)
|
243
156
|
|
244
|
-
<
|
245
|
-
<code>
|
246
|
-
require "amqp"
|
247
|
-
require "yaml"
|
248
|
-
|
249
|
-
t = Thread.new { EventMachine.run }
|
250
|
-
sleep(0.5)
|
251
|
-
|
252
|
-
connection = AMQP.connect
|
253
|
-
channel = AMQP::Channel.new(connection)
|
254
|
-
|
255
|
-
# publish new commands every 3 seconds
|
256
|
-
EventMachine.add_periodic_timer(10.0) do
|
257
|
-
puts "Publishing a command (gems.install)"
|
258
|
-
payload = { :gem => "rack", :version => "~> 1.3.0" }.to_yaml
|
259
|
-
|
260
|
-
channel.default_exchange.publish(payload,
|
261
|
-
:type => "gems.install",
|
262
|
-
:routing_key => "amqpgem.examples.patterns.command")
|
263
|
-
end
|
264
|
-
|
265
|
-
puts "[boot] Ready"
|
266
|
-
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
267
|
-
t.join
|
268
|
-
</code>
|
269
|
-
</pre>
|
157
|
+
<script src="https://gist.github.com/1207761.js"> </script>
|
270
158
|
|
271
159
|
|
272
160
|
h3. Related patterns
|
@@ -292,6 +180,12 @@ Some specific use cases of Event pattern are
|
|
292
180
|
* Live sport score updates
|
293
181
|
* Various "push notifications" for mobile applications
|
294
182
|
|
183
|
+
The Event pattern is very similar to the Command pattern, however, there is typically certain differences between the two:
|
184
|
+
|
185
|
+
* Event listeners often do not respond back to event producers
|
186
|
+
* Event listeners are often concerned with data collection: they update counters, persist event information and so on
|
187
|
+
* There may be more than event listener in the system. Commands are often carried out by one particular application
|
188
|
+
|
295
189
|
|
296
190
|
h3. AMQP-based implementation
|
297
191
|
|
@@ -300,12 +194,31 @@ then use server-named exclusive queues and all bind to that exchange. Event mess
|
|
300
194
|
attribute to indicate event type and message body (plus, possibly, message headers) to pass event
|
301
195
|
context information.
|
302
196
|
|
197
|
+
h4. Request message attributes
|
198
|
+
|
199
|
+
<dl>
|
200
|
+
<dt>:type</dt>
|
201
|
+
<dd>Message type as a string. For example: files.created, files.indexed or pages.viewed</dd>
|
202
|
+
</dl>
|
203
|
+
|
204
|
+
<span class="note">
|
205
|
+
Due to misconfiguration or different upgrade time/policy, applications may receive events they do not know how to handle.
|
206
|
+
It is important for developers to handle such cases, otherwise it is likely that consumers may crash.
|
207
|
+
</span>
|
208
|
+
|
303
209
|
More on fanout exchange type in the {file:docs/Exchanges.textile Working With Exchanges} guide.
|
304
210
|
|
305
211
|
|
306
212
|
h3. Code example
|
307
213
|
|
308
|
-
|
214
|
+
h4. Producer (Sender)
|
215
|
+
|
216
|
+
<script src="https://gist.github.com/1207750.js"> </script>
|
217
|
+
|
218
|
+
|
219
|
+
h4. Consumer (Handler)
|
220
|
+
|
221
|
+
<script src="https://gist.github.com/1207749.js"> </script>
|
309
222
|
|
310
223
|
|
311
224
|
h3. Related patterns
|
@@ -315,19 +228,23 @@ h3. Related patterns
|
|
315
228
|
|
316
229
|
|
317
230
|
|
231
|
+
|
318
232
|
h2. Document Message pattern
|
319
233
|
|
320
234
|
h3. Description & Use cases
|
321
235
|
|
322
|
-
|
236
|
+
Document Message pattern is very similar to Command and Event patterns. The difference is in the intent: whereas a Command message tells
|
237
|
+
the receiver to invoke certain behavior, a Document Message just passes data and lets the receiver decide what, if anything, to do with the data.
|
323
238
|
|
324
|
-
|
239
|
+
Message payload is a single logical entity, for example, one (or a group of closely related) database rows or documents.
|
325
240
|
|
326
|
-
|
241
|
+
Use cases for the Document Message pattern often have something to do with processing of documents:
|
327
242
|
|
328
|
-
|
243
|
+
* Indexing
|
244
|
+
* Archiving
|
245
|
+
* Content extraction
|
246
|
+
* Transformation (translation, transcoding and so on) of document data
|
329
247
|
|
330
|
-
TBD
|
331
248
|
|
332
249
|
|
333
250
|
h2. Competing Consumers pattern
|
data/docs/Queues.textile
CHANGED
@@ -457,6 +457,8 @@ It can be right after receiving a message, or after persisting it to a data stor
|
|
457
457
|
processing the message (for example, successfully fetching a Web page, processing and storing it into some persistent
|
458
458
|
data store).
|
459
459
|
|
460
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/006_amqp_091_message_acknowledgements.png!
|
461
|
+
|
460
462
|
If a consumer dies without sending an acknowledgement, the AMQP broker will redeliver it to another consumer, or, if
|
461
463
|
none are available at the time, the broker will wait until at least one consumer is registered for the same queue
|
462
464
|
before attempting redelivery.
|
@@ -13,6 +13,17 @@ h2. Covered versions
|
|
13
13
|
This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later.
|
14
14
|
|
15
15
|
|
16
|
+
h2. RabbitMQ Version Requirement
|
17
|
+
|
18
|
+
amqp gem before 0.8.0 (0.6.x, 0.7.x) series implemented (most of) AMQP 0.8 specification. amqp gem 0.8.0 implements AMQP 0.9.1 and thus
|
19
|
+
*requires RabbitMQ version 2.0 or later*. See {file:docs/RabbitMQVersions.textile RabbitMQ versions} for more information about
|
20
|
+
RabbitMQ versions support and how obtain up-todate packages for your operating system.
|
21
|
+
|
22
|
+
<span class="note">
|
23
|
+
amqp gem 0.8.0 and later versions implement AMQP 0.9.1 and thus *requires RabbitMQ version 2.0 or later*
|
24
|
+
</span>
|
25
|
+
|
26
|
+
|
16
27
|
|
17
28
|
h2. Using recent versions on Debian and Ubuntu
|
18
29
|
|
@@ -22,6 +33,9 @@ that only supports AMQP protocol 0.8. Ruby amqp gem 0.8.0 and later *will not wo
|
|
22
33
|
We strongly recommend that you use "RabbitMQ apt repository":http://www.rabbitmq.com/debian.html#apt that has recent versions of RabbitMQ.
|
23
34
|
|
24
35
|
|
36
|
+
|
37
|
+
h2. OpsCode Chef & Puppet
|
38
|
+
|
25
39
|
h3. Chef cookbook for RabbitMQ
|
26
40
|
|
27
41
|
There is a "Chef cookbook for RabbitMQ":https://github.com/opscode/cookbooks/tree/master/rabbitmq that installs recent versions from
|
@@ -1,34 +1,36 @@
|
|
1
|
-
# @title Ruby AMQP gem: Testing
|
2
|
-
|
3
|
-
h1. Testing you applications with evented-spec
|
1
|
+
# @title Ruby AMQP gem: Testing AMQP applications
|
4
2
|
|
3
|
+
h1. Testing AMQP applications
|
5
4
|
|
6
5
|
h2. About this guide
|
7
6
|
|
8
|
-
This guide covers
|
7
|
+
This guide covers unit testing of amqp-based applications, primarily using "evented-spec":http://github.com/ruby-amqp/evented-spec.
|
8
|
+
|
9
9
|
|
10
10
|
|
11
11
|
h2. Covered versions
|
12
12
|
|
13
|
-
This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later
|
14
|
-
|
13
|
+
This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later as well as
|
14
|
+
"evented-spec gem":http://github.com/ruby-amqp/evented-spec v0.9.0 and later.
|
15
|
+
|
15
16
|
|
16
17
|
h2. Rationale
|
17
18
|
|
18
|
-
|
19
|
-
There are two
|
19
|
+
The AMQP protocol is inherently asynchronous. Testing of asynchronous code is often more difficult
|
20
|
+
than synchronous code. There are two approaches to it:
|
20
21
|
|
21
22
|
* Stubbing out a big chunk of the environment
|
22
23
|
* Using the "real" environment
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
The former is risky because your application becomes divorced from actual behavior of other applications.
|
26
|
+
The latter approach is more reliable but at the same time more tedious, because there is certain amount of incidental complexity
|
27
|
+
that "real" environment carries.
|
27
28
|
|
28
|
-
However,
|
29
|
-
|
29
|
+
However, a lot of this complexity can be eliminated with tools and libraries. The evented-spec gem is one of those tools. It grew
|
30
|
+
out of necessity to test "amqp Ruby gem":http://github.com/ruby-amqp/amqp and has provent itself to be both very powerful and easy to
|
31
|
+
use. This guide covers usage of that gem in context of applications that use amqp gem but can also be useful for testing EventMachine and
|
32
|
+
Cool.io-based applications.
|
30
33
|
|
31
|
-
This guide covers usage of that gem in context of amqp but there are all the parts for testing EM-based and Cool.io-based applications.
|
32
34
|
|
33
35
|
h2. Using evented-spec
|
34
36
|
|
@@ -39,27 +41,49 @@ calls to your examples:
|
|
39
41
|
|
40
42
|
<script src="https://gist.github.com/1027377.js"></script>
|
41
43
|
|
42
|
-
|
44
|
+
|
45
|
+
|
46
|
+
h3. Testing in the Asynchronous Environment
|
43
47
|
|
44
48
|
Since we are using callback mechanisms in order to provide asynchronicity, we have to deal with situation when we expect a response,
|
45
49
|
and response never comes. Usual solution includes setting a timeout which makes the given tests fail if they aren't finished in a timely
|
46
50
|
manner. When <code>#done</code> is called, your tests confirm successful ending of specs. Try removing <code>done</code> from the above
|
47
51
|
example and see what happens. (spoiler: <code>EventedSpec::SpecHelper::SpecTimeoutExceededError: Example timed out</code>)
|
48
52
|
|
49
|
-
h3. Changing default connection options and default timeout
|
50
53
|
|
51
|
-
|
54
|
+
|
55
|
+
h3. The #done method
|
56
|
+
|
57
|
+
The *#done* method call is a hint for evented-spec to consider the example finished. If this method is not called, example will be forcefully
|
58
|
+
terminated after a certain period of time or "time out". This means there are two approaches to testing of asynchronous code:
|
59
|
+
|
60
|
+
* Have timeout value high enough for all operations to finish (for example, expected number of messages is received).
|
61
|
+
* Call #done when some condition holds true (for example, message with a specific property or payload is received).
|
62
|
+
|
63
|
+
The latter approach is recommended because it makes tests less dependent on machine-specific throughput or timing: it is very
|
64
|
+
common for continuous integration environments to use virtual machines that are significantly less powerful than machines developers
|
65
|
+
use, so timeouts have to be carefully adjusted to work in both settings.
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
h3. Default Connection Options and Timeout
|
70
|
+
|
71
|
+
It is sometimes desirable to use custom connection settings for your test environment as well as the default timeout value used. evented-spec lets you do
|
72
|
+
it:
|
52
73
|
|
53
74
|
<script src="https://gist.github.com/1027410.js"> </script>
|
54
75
|
|
55
|
-
|
76
|
+
Available options are passed to {AMQP.connect} so it is possible to specify host, port, vhost, username and password your test suite needs.
|
77
|
+
|
78
|
+
|
56
79
|
|
57
|
-
h3. Callbacks
|
80
|
+
h3. Lifecycle Callbacks
|
58
81
|
|
59
82
|
evented-spec provides various callbacks similar to rspec's <code>before(:each)</code> / <code>after(:each)</code>. They are called <code>amqp_before</code> and
|
60
83
|
<code>amqp_after</code> and happen right after connection is established or before connection is closed. It is a good place to put your channel initialization routines.
|
61
84
|
|
62
|
-
|
85
|
+
|
86
|
+
h3. Full Example
|
63
87
|
|
64
88
|
Now that you're filled on theory part, it's time to do something with all this knowledge. Below goes a slightly modified version of one of the integration specs from AMQP
|
65
89
|
suite. It sets up default topic exchange and publishes various messages about sports events:
|
@@ -69,7 +93,9 @@ suite. It sets up default topic exchange and publishes various messages about sp
|
|
69
93
|
Couple of things to notice: <code>#done</code> is invoked using an optional callback and optional delay, also instance variables behavior in hooks is the same as in "normal" rspec
|
70
94
|
hooks.
|
71
95
|
|
72
|
-
|
96
|
+
|
97
|
+
|
98
|
+
h3. Using #delayed
|
73
99
|
|
74
100
|
AMQP gem uses "EventMachine":http://eventmachine.rubyforge.org/ under hood. If you don't know about eventmachine, you can read more about it on the official site.
|
75
101
|
What's important for us is that you *cannot use <code>sleep</code> for delays*. Why? Because all the specs code is processed directly in the "reactor":http://en.wikipedia.org/wiki/Reactor_pattern thread, if you
|
@@ -83,16 +109,34 @@ In the following example, we declare two channels, then declare the same queue t
|
|
83
109
|
|
84
110
|
If you draw a timeline, various events happen at 0.0s, then at 0.1s, then at 0.3s and eventually at 0.4s.
|
85
111
|
|
86
|
-
h3. What happens under hood
|
87
112
|
|
88
|
-
|
89
|
-
|
113
|
+
h3. Design For Testability
|
114
|
+
|
115
|
+
As *Integration With Objects* section of the {file:docs/GettingStarted.textile Getting Started with Ruby amqp gem and RabbitMQ} demonstrates, good object-oriented design
|
116
|
+
often makes it possible to test AMQP consumers in isolation without connecting to the broker or even starting EventMachine even loop. All the "Design for testability"
|
117
|
+
practices apply fully to AMQP application testing.
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
h3. Real worldExamples
|
122
|
+
|
123
|
+
Please refer to the "amqp gem test suite":https://github.com/ruby-amqp/amqp/tree/master/spec to see evented-spec in action.
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
h3. How evented-spec Works
|
128
|
+
|
129
|
+
When you include <code>EventedSpec::AMQPSpec</code> module, <code>#it</code> calls are wrapped in <code>EventMachine.start</code> + <code>AMQP.connect</code>
|
130
|
+
calls, so you can start writing your examples as if you're connected. Please note that you still need to open your own channel(s).
|
131
|
+
|
90
132
|
|
91
133
|
|
92
134
|
h2. What to read next
|
93
135
|
|
94
|
-
There is a lot more to evented-spec than described in this guide. evented-spec
|
95
|
-
|
136
|
+
There is a lot more to evented-spec than described in this guide. "evented-spec documentation":http://rdoc.info/github/ruby-amqp/evented-spec/master
|
137
|
+
covers that gem in more detail gem. For more code examples, see "amqp Ruby gem test suite":https://github.com/ruby-amqp/amqp/tree/master/spec.
|
138
|
+
|
139
|
+
|
96
140
|
|
97
141
|
h2. Tell us what you think!
|
98
142
|
|
@@ -72,6 +72,10 @@ In some situations not a single message can be lost. The only reliable way of do
|
|
72
72
|
Publisher confirms are similar to message acknowledgements documented in the {file:docs/Queues.textile Working With Queues} guide but involve publisher and AMQP broker
|
73
73
|
instead of consumer and AMQP broker.
|
74
74
|
|
75
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/006_amqp_091_message_acknowledgements.png!
|
76
|
+
|
77
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/007_rabbitmq_publisher_confirms.png!
|
78
|
+
|
75
79
|
|
76
80
|
h3. Public API
|
77
81
|
|
Binary file
|
Binary file
|
@@ -43,7 +43,7 @@ AMQP.start(:host => "localhost") do |connection, open_ok|
|
|
43
43
|
|
44
44
|
Signal.trap "TERM", show_stopper
|
45
45
|
Signal.trap "INT", show_stopper
|
46
|
-
EM.add_timer(45, show_stopper)
|
46
|
+
EM.add_timer(ENV.fetch("TIMER", 45), show_stopper)
|
47
47
|
|
48
48
|
|
49
49
|
puts "This example needs another script/app to publish messages to amq.fanout. See examples/error_handling/hello_world_producer.rb for example"
|
@@ -12,7 +12,7 @@ require 'amqp'
|
|
12
12
|
puts "=> Connection loss is detected and handled"
|
13
13
|
puts
|
14
14
|
AMQP.start(:port => 5672,
|
15
|
-
:vhost => "
|
15
|
+
:vhost => "amq_client_testbed",
|
16
16
|
:user => "amq_client_gem",
|
17
17
|
:password => "amq_client_gem_password",
|
18
18
|
:timeout => 0.3,
|
@@ -16,17 +16,29 @@ AMQP.start(:host => "localhost") do |connection, open_ok|
|
|
16
16
|
raise connection_close.reply_text
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
connection.on_tcp_connection_loss do |conn, settings|
|
20
|
+
puts "[network failure] Trying to reconnect..."
|
21
|
+
conn.reconnect(false, 2)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
ch1 = AMQP::Channel.new(connection, 2, :auto_recovery => true)
|
20
26
|
ch1.on_error do |ch, channel_close|
|
21
27
|
raise channel_close.reply_text
|
22
28
|
end
|
23
29
|
|
24
30
|
|
25
31
|
exchange = ch1.fanout("amq.fanout", :durable => true)
|
26
|
-
EventMachine.add_periodic_timer(0.
|
27
|
-
puts "Publishing..."
|
32
|
+
EventMachine.add_periodic_timer(0.9) do
|
33
|
+
puts "Publishing via default exchange..."
|
34
|
+
# messages must be routable & there must be at least one consumer.
|
35
|
+
ch1.default_exchange.publish("Routed via default_exchange", :routing_key => "amqpgem.examples.autorecovery.queue")
|
36
|
+
end
|
37
|
+
|
38
|
+
EventMachine.add_periodic_timer(0.8) do
|
39
|
+
puts "Publishing via amq.fanout..."
|
28
40
|
# messages must be routable & there must be at least one consumer.
|
29
|
-
exchange.publish("
|
41
|
+
exchange.publish("Routed via amq.fanout", :immediate => true, :mandatory => true)
|
30
42
|
end
|
31
43
|
|
32
44
|
|
@@ -36,7 +48,7 @@ AMQP.start(:host => "localhost") do |connection, open_ok|
|
|
36
48
|
|
37
49
|
Signal.trap "TERM", show_stopper
|
38
50
|
Signal.trap "INT", show_stopper
|
39
|
-
EM.add_timer(15, show_stopper)
|
51
|
+
EM.add_timer(ENV.fetch("TIMER", 15), show_stopper)
|
40
52
|
|
41
53
|
puts "This example a helper that publishes messages to amq.fanout. Use together with examples/error_handling/automatically_recovering_hello_world_consumer.rb."
|
42
54
|
puts "This example terminates in 15 seconds and needs MANUAL RESTART when connection fails"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
Bundler.setup
|
6
|
+
|
7
|
+
$:.unshift(File.expand_path("../../../lib", __FILE__))
|
8
|
+
|
9
|
+
require 'amqp'
|
10
|
+
|
11
|
+
|
12
|
+
puts "=> Queue redeclaration with different attributes results in a channel exception that is handled"
|
13
|
+
puts
|
14
|
+
EventMachine.run do
|
15
|
+
connection = AMQP.connect
|
16
|
+
AMQP::Channel.new(connection, :auto_recovery => true) do |channel, open_ok|
|
17
|
+
puts "Channel ##{channel.id} is now open!"
|
18
|
+
|
19
|
+
connection.on_error do |conn, connection_close|
|
20
|
+
puts <<-ERR
|
21
|
+
Handling a connection-level exception:
|
22
|
+
|
23
|
+
connection_close.reply_text: #{connection_close.reply_text}
|
24
|
+
ERR
|
25
|
+
end
|
26
|
+
|
27
|
+
channel.on_error do |ch, channel_close|
|
28
|
+
puts <<-ERR
|
29
|
+
Handling a channel-level exception.
|
30
|
+
|
31
|
+
AMQP class id : #{channel_close.class_id},
|
32
|
+
AMQP method id: #{channel_close.method_id},
|
33
|
+
Status code : #{channel_close.reply_code}
|
34
|
+
Error message : #{channel_close.reply_text}
|
35
|
+
ERR
|
36
|
+
|
37
|
+
puts "Reusing channel #{ch.id}"
|
38
|
+
ch.reuse
|
39
|
+
puts "Channel id is now #{ch.id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
EventMachine.add_timer(1.0) do
|
43
|
+
# these two definitions result in a race condition. For sake of this example,
|
44
|
+
# however, it does not matter. Whatever definition succeeds first, 2nd one will
|
45
|
+
# cause a channel-level exception (because attributes are not identical)
|
46
|
+
AMQP::Queue.new(channel, "amqpgem.examples.channel_exception", :auto_delete => true, :durable => false) do |queue|
|
47
|
+
puts "#{queue.name} is ready to go"
|
48
|
+
end
|
49
|
+
|
50
|
+
AMQP::Queue.new(channel, "amqpgem.examples.channel_exception", :auto_delete => true, :durable => true) do |queue|
|
51
|
+
puts "#{queue.name} is ready to go"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
show_stopper = Proc.new do
|
58
|
+
$stdout.puts "Stopping..."
|
59
|
+
|
60
|
+
connection.close {
|
61
|
+
EM.stop { exit }
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
Signal.trap "INT", show_stopper
|
66
|
+
# EM.add_timer(2, show_stopper)
|
67
|
+
end
|
@@ -11,7 +11,7 @@ sleep(0.5)
|
|
11
11
|
connection = AMQP.connect
|
12
12
|
channel = AMQP::Channel.new(connection)
|
13
13
|
|
14
|
-
# publish new commands every
|
14
|
+
# publish new commands every few seconds
|
15
15
|
EventMachine.add_periodic_timer(10.0) do
|
16
16
|
puts "Publishing a command (gems.install)"
|
17
17
|
payload = { :gem => "rack", :version => "~> 1.3.0" }.to_yaml
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../../../../lib", __FILE__)
|
4
|
+
|
5
|
+
require "amqp"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
t = Thread.new { EventMachine.run }
|
9
|
+
sleep(0.5)
|
10
|
+
|
11
|
+
|
12
|
+
connection = AMQP.connect
|
13
|
+
channel = AMQP::Channel.new(connection, :auto_recovery => true)
|
14
|
+
channel.on_error do |ch, channel_close|
|
15
|
+
raise "Channel-level exception: #{channel_close.reply_text}"
|
16
|
+
end
|
17
|
+
|
18
|
+
channel.prefetch(1)
|
19
|
+
|
20
|
+
channel.queue("", :durable => false, :auto_delete => true).bind("amqpgem.patterns.events").subscribe do |metadata, payload|
|
21
|
+
begin
|
22
|
+
body = YAML.load(payload)
|
23
|
+
|
24
|
+
case metadata.type
|
25
|
+
when "widgets.created" then
|
26
|
+
puts "A widget #{body[:id]} was created"
|
27
|
+
when "widgets.destroyed" then
|
28
|
+
puts "A widget #{body[:id]} was destroyed"
|
29
|
+
when "files.created" then
|
30
|
+
puts "A new file (#{body[:filename]}, #{body[:sha1]}) was uploaded"
|
31
|
+
when "files.indexed" then
|
32
|
+
puts "A new file (#{body[:filename]}, #{body[:sha1]}) was indexed"
|
33
|
+
else
|
34
|
+
puts "[warn] Do not know how to handle event of type #{metadata.type}"
|
35
|
+
end
|
36
|
+
rescue Exception => e
|
37
|
+
puts "[error] Could not handle event of type #{metadata.type}: #{e.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "[boot] Ready"
|
42
|
+
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
43
|
+
t.join
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../../../../lib", __FILE__)
|
4
|
+
|
5
|
+
require "amqp"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
t = Thread.new { EventMachine.run }
|
9
|
+
sleep(0.5)
|
10
|
+
|
11
|
+
connection = AMQP.connect
|
12
|
+
channel = AMQP::Channel.new(connection)
|
13
|
+
exchange = channel.fanout("amqpgem.patterns.events", :durable => true, :auto_delete => false)
|
14
|
+
|
15
|
+
|
16
|
+
EVENTS = {
|
17
|
+
"pages.show" => {
|
18
|
+
:url => "https://mysite.local/widgets/81772",
|
19
|
+
:referrer => "http://www.google.com/search?client=safari&rls=en&q=widgets&ie=UTF-8&oe=UTF-8"
|
20
|
+
},
|
21
|
+
"widgets.created" => {
|
22
|
+
:id => 10,
|
23
|
+
:shape => "round",
|
24
|
+
:owner_id => 1000
|
25
|
+
},
|
26
|
+
"widgets.destroyed" => {
|
27
|
+
:id => 10,
|
28
|
+
:person_id => 1000
|
29
|
+
},
|
30
|
+
"files.created" => {
|
31
|
+
:sha1 => "1a62429f47bc8b405d17e84b648f2fbebc555ee5",
|
32
|
+
:filename => "document.pdf"
|
33
|
+
},
|
34
|
+
"files.indexed" => {
|
35
|
+
:sha1 => "1a62429f47bc8b405d17e84b648f2fbebc555ee5",
|
36
|
+
:filename => "document.pdf",
|
37
|
+
:runtime => 1.7623,
|
38
|
+
:shared => "shard02"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
def generate_event
|
43
|
+
n = (EVENTS.size * Kernel.rand).floor
|
44
|
+
type = EVENTS.keys[n]
|
45
|
+
payload = EVENTS[type]
|
46
|
+
|
47
|
+
[type, payload]
|
48
|
+
end
|
49
|
+
|
50
|
+
# broadcast events
|
51
|
+
EventMachine.add_periodic_timer(2.0) do
|
52
|
+
event_type, payload = generate_event
|
53
|
+
|
54
|
+
puts "Publishing a new event of type #{event_type}"
|
55
|
+
exchange.publish(payload.to_yaml, :type => event_type)
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "[boot] Ready. Will be publishing events every few seconds."
|
59
|
+
Signal.trap("INT") { connection.close { EventMachine.stop } }
|
60
|
+
t.join
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
Bundler.setup
|
6
|
+
|
7
|
+
$:.unshift(File.expand_path("../../../lib", __FILE__))
|
8
|
+
|
9
|
+
require 'amqp'
|
10
|
+
|
11
|
+
EventMachine.run do
|
12
|
+
connection = AMQP.connect(:host => '127.0.0.1')
|
13
|
+
puts "Connected to AMQP broker. Running #{AMQP::VERSION} version of the gem..."
|
14
|
+
|
15
|
+
channel = AMQP::Channel.new(connection)
|
16
|
+
queue = channel.queue("amqpgem.examples.hello_world", :auto_delete => true)
|
17
|
+
exchange = channel.direct("amq.direct")
|
18
|
+
|
19
|
+
queue.bind(exchange, :routing_key => "amqpgem.key")
|
20
|
+
|
21
|
+
channel.on_error do |ch, channel_close|
|
22
|
+
puts channel_close.reply_text
|
23
|
+
connection.close { EventMachine.stop }
|
24
|
+
end
|
25
|
+
|
26
|
+
queue.subscribe do |metadata, payload|
|
27
|
+
puts "Received a message: #{payload}."
|
28
|
+
end
|
29
|
+
|
30
|
+
EventMachine.add_periodic_timer(1.0) do
|
31
|
+
exchange.publish("Hey, what a great view!", :routing_key => "amqpgem.key")
|
32
|
+
end
|
33
|
+
|
34
|
+
EventMachine.add_timer(3.0) do
|
35
|
+
queue.unsubscribe do
|
36
|
+
puts "Cancelled default consumer..."
|
37
|
+
connection.close { EventMachine.stop }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/amqp/channel.rb
CHANGED
@@ -265,7 +265,7 @@ module AMQP
|
|
265
265
|
@channel_is_open_deferrable.succeed
|
266
266
|
|
267
267
|
# exchanges must be recovered first because queue recovery includes recovery of bindings. MK.
|
268
|
-
@exchanges.each { |name, e| e.auto_recover }
|
268
|
+
@exchanges.each { |name, e| puts("Recovering ex #{name}"); e.auto_recover }
|
269
269
|
@queues.each { |name, q| q.auto_recover }
|
270
270
|
end
|
271
271
|
end # auto_recover
|
@@ -423,7 +423,7 @@ module AMQP
|
|
423
423
|
# @return [Exchange]
|
424
424
|
# @api public
|
425
425
|
def default_exchange
|
426
|
-
Exchange.default(self)
|
426
|
+
@default_exchange ||= Exchange.default(self)
|
427
427
|
end
|
428
428
|
|
429
429
|
# Defines, intializes and returns a fanout Exchange instance.
|
data/lib/amqp/exchange.rb
CHANGED
@@ -300,12 +300,12 @@ module AMQP
|
|
300
300
|
@channel = channel
|
301
301
|
@type = type
|
302
302
|
@opts = self.class.add_default_options(type, name, opts, block)
|
303
|
-
@default_routing_key = opts[:routing_key] || opts[:key]
|
303
|
+
@default_routing_key = opts[:routing_key] || opts[:key] || AMQ::Protocol::EMPTY_STRING
|
304
304
|
@name = name unless name.empty?
|
305
305
|
|
306
306
|
@status = :unknown
|
307
307
|
@default_publish_options = (opts.delete(:default_publish_options) || {
|
308
|
-
:routing_key =>
|
308
|
+
:routing_key => @default_routing_key,
|
309
309
|
:mandatory => false,
|
310
310
|
:immediate => false
|
311
311
|
}).freeze
|
data/lib/amqp/queue.rb
CHANGED
@@ -775,7 +775,7 @@ module AMQP
|
|
775
775
|
# unsubscription request is acknowledged as complete by the server.
|
776
776
|
#
|
777
777
|
# @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
|
778
|
-
# not wait for a reply method.
|
778
|
+
# not wait for a reply method, the callback (if passed) will be ignored. If the server could not complete the
|
779
779
|
# method it will raise a channel or connection exception.
|
780
780
|
#
|
781
781
|
# @yield [cancel_ok]
|
data/lib/amqp/version.rb
CHANGED
@@ -23,6 +23,75 @@ describe AMQP::Channel do
|
|
23
23
|
# Examples
|
24
24
|
#
|
25
25
|
|
26
|
+
|
27
|
+
describe "default exchange" do
|
28
|
+
subject do
|
29
|
+
@channel.default_exchange
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is predefined" do
|
33
|
+
subject.should be_predefined
|
34
|
+
done
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "exchange named amq.direct" do
|
39
|
+
subject do
|
40
|
+
@channel.direct("amq.direct")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is predefined" do
|
44
|
+
subject.should be_predefined
|
45
|
+
done
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "exchange named amq.fanout" do
|
50
|
+
subject do
|
51
|
+
@channel.direct("amq.fanout")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is predefined" do
|
55
|
+
subject.should be_predefined
|
56
|
+
done
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "exchange named amq.topic" do
|
61
|
+
subject do
|
62
|
+
@channel.direct("amq.topic")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is predefined" do
|
66
|
+
subject.should be_predefined
|
67
|
+
done
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "exchange named amq.match" do
|
72
|
+
subject do
|
73
|
+
@channel.direct("amq.match")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "is predefined" do
|
77
|
+
subject.should be_predefined
|
78
|
+
done
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "exchange named amq.headers" do
|
83
|
+
subject do
|
84
|
+
@channel.direct("amq.headers")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "is predefined" do
|
88
|
+
subject.should be_predefined
|
89
|
+
done
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
26
95
|
describe "#direct" do
|
27
96
|
context "when exchange name is specified" do
|
28
97
|
it 'declares a new direct exchange with that name' do
|
@@ -61,7 +61,7 @@ if mri?
|
|
61
61
|
queue = @channel.queue("amqpgem.tests.concurrent_publishing", :auto_delete => true)
|
62
62
|
exchange = @channel.default_exchange
|
63
63
|
exchange.on_return do |method, header, body|
|
64
|
-
|
64
|
+
puts "Message was returned: #{method.reply_text}"
|
65
65
|
end
|
66
66
|
|
67
67
|
queue.subscribe do |metadata, payload|
|
metadata
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amqp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 1923831867
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
- pre
|
11
|
+
- 1
|
12
|
+
version: 0.9.0.pre1
|
11
13
|
platform: ruby
|
12
14
|
authors:
|
13
15
|
- Aman Gupta
|
@@ -17,7 +19,7 @@ autorequire:
|
|
17
19
|
bindir: bin
|
18
20
|
cert_chain: []
|
19
21
|
|
20
|
-
date:
|
22
|
+
date: 2012-01-01 00:00:00 Z
|
21
23
|
dependencies:
|
22
24
|
- !ruby/object:Gem::Dependency
|
23
25
|
name: eventmachine
|
@@ -131,6 +133,8 @@ files:
|
|
131
133
|
- docs/diagrams/003_weathr_example_routing.png
|
132
134
|
- docs/diagrams/004_fanout_exchange.png
|
133
135
|
- docs/diagrams/005_direct_exchange.png
|
136
|
+
- docs/diagrams/006_amqp_091_message_acknowledgements.png
|
137
|
+
- docs/diagrams/007_rabbitmq_publisher_confirms.png
|
134
138
|
- docs/diagrams/redhat/direct_exchange.png
|
135
139
|
- docs/diagrams/redhat/fanout_exchange.png
|
136
140
|
- docs/diagrams/redhat/topic_exchange.png
|
@@ -159,6 +163,7 @@ files:
|
|
159
163
|
- examples/error_handling/manual_connection_and_channel_recovery.rb
|
160
164
|
- examples/error_handling/queue_exclusivity_violation.rb
|
161
165
|
- examples/error_handling/queue_name_violation.rb
|
166
|
+
- examples/error_handling/reopening_a_channel_after_channel_level_exception.rb
|
162
167
|
- examples/error_handling/tcp_connection_failure_handling_with_a_rescue_block.rb
|
163
168
|
- examples/error_handling/tcp_connection_failure_with_a_callback.rb
|
164
169
|
- examples/exchanges/autodeletion_of_exchanges.rb
|
@@ -210,6 +215,8 @@ files:
|
|
210
215
|
- examples/legacy/stocks.rb
|
211
216
|
- examples/patterns/command/consumer.rb
|
212
217
|
- examples/patterns/command/producer.rb
|
218
|
+
- examples/patterns/event/consumer.rb
|
219
|
+
- examples/patterns/event/producer.rb
|
213
220
|
- examples/patterns/request_reply/client.rb
|
214
221
|
- examples/patterns/request_reply/server.rb
|
215
222
|
- examples/publishing/publishing_a_one_off_message.rb
|
@@ -219,6 +226,7 @@ files:
|
|
219
226
|
- examples/queues/accessing_message_metadata.rb
|
220
227
|
- examples/queues/automatic_binding_for_default_direct_exchange.rb
|
221
228
|
- examples/queues/basic_get.rb
|
229
|
+
- examples/queues/cancel_default_consumer.rb
|
222
230
|
- examples/queues/declare_a_queue_without_assignment.rb
|
223
231
|
- examples/queues/declare_and_bind_a_server_named_queue.rb
|
224
232
|
- examples/queues/queue_status.rb
|
@@ -348,12 +356,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
348
356
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
349
357
|
none: false
|
350
358
|
requirements:
|
351
|
-
- - "
|
359
|
+
- - ">"
|
352
360
|
- !ruby/object:Gem::Version
|
353
|
-
hash:
|
361
|
+
hash: 25
|
354
362
|
segments:
|
355
|
-
-
|
356
|
-
|
363
|
+
- 1
|
364
|
+
- 3
|
365
|
+
- 1
|
366
|
+
version: 1.3.1
|
357
367
|
requirements: []
|
358
368
|
|
359
369
|
rubyforge_project: amqp
|