amqp 0.8.4 → 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Continuous Integration status](https://secure.travis-ci.org/ruby-amqp/amqp.png)](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
|