amqp-subscribe-many 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "amqp", ">= 0.9.6"
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "minitest", ">= 3.0"
8
+ gem "mocha"
9
+ gem "jeweler"
10
+ gem "redcarpet"
11
+ gem "yard"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ amq-client (0.9.4)
5
+ amq-protocol (>= 0.9.4)
6
+ eventmachine
7
+ amq-protocol (0.9.4)
8
+ amqp (0.9.7)
9
+ amq-client (~> 0.9.4)
10
+ amq-protocol (>= 0.9.4)
11
+ eventmachine
12
+ eventmachine (0.12.10)
13
+ git (1.2.5)
14
+ jeweler (1.8.4)
15
+ bundler (~> 1.0)
16
+ git (>= 1.2.5)
17
+ rake
18
+ rdoc
19
+ json (1.7.3)
20
+ metaclass (0.0.1)
21
+ minitest (3.2.0)
22
+ mocha (0.12.0)
23
+ metaclass (~> 0.0.1)
24
+ rake (0.9.2.2)
25
+ rdoc (3.12)
26
+ json (~> 1.4)
27
+ redcarpet (2.1.1)
28
+ yard (0.8.2.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ amqp (>= 0.9.6)
35
+ bundler
36
+ jeweler
37
+ minitest (>= 3.0)
38
+ mocha
39
+ redcarpet
40
+ yard
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Brendan Hay
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,19 @@
1
+ BUNDLE=`which bundle`
2
+
3
+ #
4
+ # Targets
5
+ #
6
+
7
+ .PHONY: deps doc
8
+
9
+ all: deps test
10
+
11
+ deps:
12
+ $(BUNDLE) install
13
+
14
+ test:
15
+ $(BUNDLE) exec rake test
16
+
17
+ doc:
18
+ $(BUNDLE) exec rake yard
19
+
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ Publish One, Subscribe Many
2
+ ===========
3
+
4
+ [![Build Status](https://secure.travis-ci.org/brendanhay/amqp-subscribe-many.png)](http://travis-ci.org/brendanhay/amqp-subscribe-many)
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ #
2
+ # Bundler
3
+ #
4
+
5
+ require "rubygems"
6
+ require "bundler"
7
+
8
+ begin
9
+ Bundler.setup(:default)
10
+ rescue Bundler::BundlerError => ex
11
+ $stderr.puts ex.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit ex.status_code
14
+ end
15
+
16
+
17
+ #
18
+ # Tests
19
+ #
20
+
21
+ require "rake"
22
+ require "rake/testtask"
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.libs.concat ["lib", "test"]
26
+ t.test_files = FileList["test/*_test.rb"]
27
+ t.verbose = true
28
+ end
29
+
30
+ task :default => :test
31
+
32
+
33
+ #
34
+ # Console
35
+ #
36
+
37
+ task :console do
38
+ sh "irb -rubygems -I lib -r messaging.rb"
39
+ end
40
+
41
+
42
+ #
43
+ # Gemify
44
+ #
45
+
46
+ require "jeweler"
47
+
48
+ Jeweler::RubygemsDotOrgTasks.new
49
+
50
+ Jeweler::Tasks.new do |gem|
51
+ gem.name = "amqp-subscribe-many"
52
+ gem.version = "0.1.1"
53
+ gem.homepage = "http://github.com/brendanhay/amqp-subscribe-many"
54
+ gem.license = "BSD"
55
+ gem.summary = "'Publish-one, subscribe-many' pattern implementation"
56
+ gem.description = gem.summary
57
+ gem.email = "brendan@soundcloud.com"
58
+ gem.authors = ["brendanhay"]
59
+ end
60
+
61
+
62
+ #
63
+ # Docs
64
+ #
65
+
66
+ require "yard"
67
+
68
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{amqp-subscribe-many}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{brendanhay}]
12
+ s.date = %q{2012-07-10}
13
+ s.description = %q{'Publish-one, subscribe-many' pattern implementation}
14
+ s.email = %q{brendan@soundcloud.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".travis.yml",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "Makefile",
25
+ "README.md",
26
+ "Rakefile",
27
+ "amqp-subscribe-many.gemspec",
28
+ "lib/messaging.rb",
29
+ "lib/messaging/client.rb",
30
+ "lib/messaging/configuration.rb",
31
+ "lib/messaging/consumer.rb",
32
+ "lib/messaging/producer.rb",
33
+ "script/config.yml",
34
+ "script/run.rb",
35
+ "test/client_test.rb",
36
+ "test/configuration_test.rb",
37
+ "test/consumer_test.rb",
38
+ "test/test_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/brendanhay/amqp-subscribe-many}
41
+ s.licenses = [%q{BSD}]
42
+ s.require_paths = [%q{lib}]
43
+ s.rubygems_version = %q{1.8.7}
44
+ s.summary = %q{'Publish-one, subscribe-many' pattern implementation}
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<amqp>, [">= 0.9.6"])
51
+ s.add_development_dependency(%q<bundler>, [">= 0"])
52
+ s.add_development_dependency(%q<minitest>, [">= 3.0"])
53
+ s.add_development_dependency(%q<mocha>, [">= 0"])
54
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
55
+ s.add_development_dependency(%q<redcarpet>, [">= 0"])
56
+ s.add_development_dependency(%q<yard>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<amqp>, [">= 0.9.6"])
59
+ s.add_dependency(%q<bundler>, [">= 0"])
60
+ s.add_dependency(%q<minitest>, [">= 3.0"])
61
+ s.add_dependency(%q<mocha>, [">= 0"])
62
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<redcarpet>, [">= 0"])
64
+ s.add_dependency(%q<yard>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<amqp>, [">= 0.9.6"])
68
+ s.add_dependency(%q<bundler>, [">= 0"])
69
+ s.add_dependency(%q<minitest>, [">= 3.0"])
70
+ s.add_dependency(%q<mocha>, [">= 0"])
71
+ s.add_dependency(%q<jeweler>, [">= 0"])
72
+ s.add_dependency(%q<redcarpet>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ end
75
+ end
76
+
data/lib/messaging.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative "messaging/configuration"
2
+ require_relative "messaging/client"
3
+ require_relative "messaging/producer"
4
+ require_relative "messaging/consumer"
@@ -0,0 +1,148 @@
1
+ require "amqp"
2
+
3
+ module Messaging
4
+
5
+ #
6
+ # Raised when unrecoverable connection and channel errors are encountered.
7
+ #
8
+ class MessagingError < StandardError; end
9
+
10
+ #
11
+ # Provides methods and constants required to establish an AMQP
12
+ # connection and channel with failure handling and recovery.
13
+ # @see http://www.rabbitmq.com/amqp-0-9-1-reference.html#constants
14
+ # For a list of error codes that will cause an exception to be raised
15
+ # rather than invoking automatic recovery.
16
+ #
17
+ module Client
18
+
19
+ # Create an AMQP::Connection with auto-reconnect and error handling.
20
+ #
21
+ # @param uri [String] The AMQP URI to connect to.
22
+ # @param delay [Integer, nil] Time to delay between reconnection attempts.
23
+ # @return [AMQP::Connection]
24
+ # @api public
25
+ def open_connection(uri, delay = nil)
26
+ delay ||= config.reconnect_delay
27
+ options = AMQP::Client.parse_connection_uri(uri)
28
+
29
+ AMQP.connect(options) do |connection, open_ok|
30
+ # Handle TCP connection errors
31
+ connection.on_tcp_connection_loss do |conn, settings|
32
+ log.error("Connection to #{uri.inspect} lost, reconnecting")
33
+
34
+ conn.periodically_reconnect(delay)
35
+ end
36
+
37
+ # Handle general errors
38
+ connection.on_error do |conn, error|
39
+ log.error("Connection to #{uri.inspect} lost, reconnecting")
40
+
41
+ if (402..540).include?(error.reply_code)
42
+ raise(MessagingError, "Channel exception: #{error.reply_text.inspect}")
43
+ end
44
+
45
+ conn.periodically_reconnect(delay)
46
+ end
47
+
48
+ log.debug("Connection to #{uri.inspect} started")
49
+ end
50
+ end
51
+
52
+ # Open an AMQP::Channel with auto-recovery and error handling.
53
+ #
54
+ # @param connection [AMQP::Connection]
55
+ # @param prefetch [Integer, nil]
56
+ # @return [AMQP::Channel]
57
+ # @api public
58
+ def open_channel(connection, prefetch = nil)
59
+ AMQP::Channel.new(connection) do |channel, open_ok|
60
+ channel.auto_recovery = true
61
+ channel.prefetch(prefetch) if prefetch
62
+
63
+ channel.on_error do |ch, error|
64
+ log.error("Channel error #{error.reply_text.inspect}, recovering")
65
+
66
+ # Raise erroneous channel calls/conditions
67
+ # rather than endlessly retrying
68
+ if (403..406).include?(error.reply_code)
69
+ raise(MessagingError, "Channel exception: #{error.reply_text.inspect}")
70
+ end
71
+ end
72
+
73
+ log.debug("Channel #{channel.id} created")
74
+ end
75
+ end
76
+
77
+ # Declare an exchange on the specified channel.
78
+ #
79
+ # @param channel [AMQP::Channel]
80
+ # @param name [String]
81
+ # @param type [String]
82
+ # @param options [Hash]
83
+ # @return [AMQP::Exchange]
84
+ # @api public
85
+ def declare_exchange(channel, name, type, options = {})
86
+ exchange =
87
+ # Check if default options need to be supplied to a non-default delcaration
88
+ if default_exchange?(name)
89
+ channel.default_exchange
90
+ else
91
+ channel.send(type, name, options)
92
+ end
93
+
94
+ log.debug("Exchange #{exchange.name.inspect} declared")
95
+
96
+ exchange
97
+ end
98
+
99
+ # Declare and bind a queue to the specified exchange via the
100
+ # supplied routing key.
101
+ #
102
+ # @param channel [AMQP::Channel]
103
+ # @param exchange [AMQP::Exchange]
104
+ # @param name [String]
105
+ # @param key [String]
106
+ # @param options [Hash]
107
+ # @return [AMQP::Queue]
108
+ # @api public
109
+ def declare_queue(channel, exchange, name, key, options = {})
110
+ channel.queue(name, options) do |queue|
111
+ # Check if additional bindings are needed
112
+ unless default_exchange?(exchange.name)
113
+ queue.bind(exchange, { :routing_key => key })
114
+ end
115
+
116
+ log.debug("Queue #{queue.name.inspect} bound to #{exchange.name.inspect}")
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ # @return [#info, #debug, #error]
123
+ # @api protected
124
+ def log
125
+ config.logger
126
+ end
127
+
128
+ # @return [Messaging::Configuration]
129
+ # @api protected
130
+ def config
131
+ Configuration.instance
132
+ end
133
+
134
+ private
135
+
136
+ # @param name [String]
137
+ # @return [Boolean]
138
+ # @api private
139
+ def default_exchange?(name)
140
+ ["amq.direct",
141
+ "amq.fanout",
142
+ "amq.topic",
143
+ "amqp.headers",
144
+ "amqp.match"].include?(name)
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,56 @@
1
+ require "singleton"
2
+ require "logger"
3
+
4
+ module Messaging
5
+
6
+ # Global configuration for producer and consumer mixins.
7
+ class Configuration
8
+ include Singleton
9
+
10
+ # @yieldparam [Messaging::Configuration] config
11
+ # @api public
12
+ def self.setup(&block)
13
+ yield(Configuration.instance)
14
+ end
15
+
16
+ # @!attribute [r] publish_to
17
+ # @return [String]
18
+ attr_accessor :publish_to
19
+
20
+ # @!attribute [r] consume_from
21
+ # @return [Array<String>]
22
+ attr_accessor :consume_from
23
+
24
+ # @!attribute [r] prefetch
25
+ # @return [Integer]
26
+ attr_accessor :prefetch
27
+
28
+ # @!attribute [r] exchange_options
29
+ # @return [Hash]
30
+ attr_accessor :exchange_options
31
+
32
+ # @!attribute [r] queue_options
33
+ # @return [Hash]
34
+ attr_accessor :queue_options
35
+
36
+ # @!attribute [r] reconnect_delay
37
+ # @return [Integer]
38
+ attr_accessor :reconnect_delay
39
+
40
+ # @!attribute [r] logger
41
+ # @return [#info, #debug, #error]
42
+ attr_accessor :logger
43
+
44
+ # @api private
45
+ def initialize
46
+ @publish_to = "amqp://guest:guest@localhost:5672"
47
+ @consume_from = [publish_to]
48
+ @prefetch = 1
49
+ @exchange_options = { :auto_delete => false, :durable => true }
50
+ @queue_options = exchange_options
51
+ @reconnect_delay = 5
52
+ @logger = Logger.new(STDOUT)
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,123 @@
1
+ module Messaging
2
+
3
+ module Consumer
4
+ include Client
5
+
6
+ # DSL methods which are used to extend the target when
7
+ # {Messaging::Consumer} is included into a class.
8
+ module Extensions
9
+
10
+ # Subscribe to a queue which will invoke {Messaging::Consumer#on_message}
11
+ # upon receiving a message.
12
+ #
13
+ # @param exchange [String]
14
+ # @param type [String]
15
+ # @param queue [String]
16
+ # @param key [String]
17
+ # @return [Array<Array(String, String, String, String)>]
18
+ # @api public
19
+ def subscribe(exchange, type, queue, key)
20
+ subscriptions << [exchange, type, queue, key]
21
+ end
22
+
23
+ # A list of subscriptions intended for internal use.
24
+ #
25
+ # @return [Array<Array(String, String, String, String)>]
26
+ # @api private
27
+ def subscriptions
28
+ @subscriptions ||= []
29
+ end
30
+
31
+ end
32
+
33
+ def self.included(base)
34
+ base.send(:extend, Extensions)
35
+ end
36
+
37
+ # @return [Messaging::Consumer]
38
+ # @api public
39
+ def consume
40
+ unless consumer_channels
41
+ @consumer_channels ||= consumer_connections.map do |conn|
42
+ open_channel(conn, config.prefetch)
43
+ end
44
+
45
+ subscriptions.each { |args| subscribe(*args) }
46
+ end
47
+
48
+ self
49
+ end
50
+
51
+ # Subscribe to a queue which will invoke the supplied block when
52
+ # a message is received.
53
+ # Additionally declaring a binding to the specified exchange/key pair.
54
+ #
55
+ # @param exchange [String]
56
+ # @param type [String]
57
+ # @param queue [String]
58
+ # @param key [String]
59
+ # @return [Messaging::Consumer]
60
+ # @api public
61
+ def subscribe(exchange, type, queue, key)
62
+ consumer_channels.each do |channel|
63
+ ex = declare_exchange(channel, exchange, type, config.exchange_options)
64
+ q = declare_queue(channel, ex, queue, key, config.queue_options)
65
+
66
+ q.subscribe(:ack => true) do |meta, payload|
67
+ log.debug("Receieved message on channel #{meta.channel.id} from queue #{queue.inspect}")
68
+
69
+ # If this raises an exception, the connection
70
+ # will be closed, and the message requeued by the broker.
71
+ on_message(meta, payload)
72
+
73
+ meta.ack
74
+ end
75
+ end
76
+
77
+ self
78
+ end
79
+
80
+ # @raise [NotImplementedError]
81
+ # @api protected
82
+ def on_message(meta, payload)
83
+ raise NotImplementedError
84
+ end
85
+
86
+ # Close all consumer_channels and then disconnect all the consumer_connections.
87
+ #
88
+ # @return []
89
+ # @api public
90
+ def disconnect
91
+ consumer_channels.each do |chan|
92
+ chan.close
93
+ end
94
+
95
+ consumer_connections.each do |conn|
96
+ conn.disconnect
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ # @return [Array<AMQP::Connection>]
103
+ # @api private
104
+ def consumer_connections
105
+ @consumer_connections ||= config.consume_from.map do |uri|
106
+ open_connection(uri)
107
+ end
108
+ end
109
+
110
+ # @return [Array<AMQP::Channel>]
111
+ # @api private
112
+ def consumer_channels
113
+ @consumer_channels
114
+ end
115
+
116
+ # @return [Array<Array(String, String, String, String)>]
117
+ # @api private
118
+ def subscriptions
119
+ self.class.subscriptions
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,59 @@
1
+ module Messaging
2
+
3
+ module Producer
4
+ include Client
5
+
6
+ # Publish a payload to the specified exchange/key pair.
7
+ #
8
+ # @param exchange [String]
9
+ # @param type [String]
10
+ # @param key [String]
11
+ # @param payload [Object]
12
+ # @return [Messaging::Producer]
13
+ # @api public
14
+ def publish(exchange, type, key, payload)
15
+ ex = producer_exchanges[exchange] ||=
16
+ declare_exchange(producer_channel, exchange, type, config.exchange_options)
17
+
18
+ log.debug("Publishing to exchange #{exchange.inspect} via #{key.inspect}")
19
+
20
+ ex.publish(payload, {
21
+ :exchange => exchange,
22
+ :routing_key => key
23
+ })
24
+
25
+ self
26
+ end
27
+
28
+ # Close the channel and then disconnect the connection.
29
+ #
30
+ # @return []
31
+ # @api public
32
+ def disconnect
33
+ producer_channel.close do |close_ok|
34
+ producer_connection.disconnect
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # @return [Hash(String, AMQP::Exchange)]
41
+ # @api private
42
+ def producer_exchanges
43
+ @producer_exchanges ||= {}
44
+ end
45
+
46
+ # @return [AMQP::Connection]
47
+ # @api private
48
+ def producer_connection
49
+ @producer_connection ||= open_connection(config.publish_to)
50
+ end
51
+
52
+ # @return [AMQP::Channel]
53
+ # @api private
54
+ def producer_channel
55
+ @producer_channel ||= open_channel(producer_connection)
56
+ end
57
+ end
58
+
59
+ end
data/script/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ :publish_to: amqp://localhost
3
+ :consume_from:
4
+ - amqp://localhost
5
+ - amqp://localhost
6
+ - amqp://localhost
data/script/run.rb ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.setup(:default)
7
+
8
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
9
+
10
+ require "messaging"
11
+
12
+ EXCHANGE = "exchange"
13
+ TYPE = "direct"
14
+ QUEUE = "queue"
15
+ KEY = "key"
16
+
17
+ # Load the config
18
+ yml = YAML::load_file(File.dirname(__FILE__) + "/config.yml")
19
+
20
+ # Setup configuration
21
+ Messaging::Configuration.setup do |config|
22
+ config.publish_to = yml[:publish_to]
23
+ config.consume_from = yml[:consume_from]
24
+ end
25
+
26
+ # Consume example
27
+ class ConsumerProcessor
28
+ include Messaging::Consumer
29
+
30
+ subscribe(EXCHANGE, TYPE, QUEUE, KEY)
31
+
32
+ def on_message(meta, payload)
33
+ log.info("ConsumeProcessor channel #{meta.channel.id} received payload #{payload.inspect}")
34
+ end
35
+ end
36
+
37
+ # Publish example
38
+ class ProducerProcessor
39
+ include Messaging::Producer
40
+ end
41
+
42
+ # Consume + publish example
43
+ class DuplexProcessor
44
+ include Messaging::Producer
45
+ include Messaging::Consumer
46
+
47
+ subscribe(EXCHANGE, TYPE, QUEUE, KEY)
48
+
49
+ def on_message(meta, payload)
50
+ log.info("DuplexProcessor channel #{meta.channel.id} received payload #{payload.inspect}")
51
+ end
52
+ end
53
+
54
+ EM.run do
55
+
56
+ # Instantiate the processors
57
+ consumer = ConsumerProcessor.new
58
+ producer = ProducerProcessor.new
59
+ duplex = DuplexProcessor.new
60
+
61
+ # Start the consumers
62
+ consumer.consume
63
+ duplex.consume
64
+
65
+ # Create a handle to the publish timer, to cancel later
66
+ timer = EM.add_periodic_timer(0.5) do
67
+ producer.publish(EXCHANGE, TYPE, KEY, "a_producer_payload")
68
+ duplex.publish(EXCHANGE, TYPE, KEY, "a_duplex_payload")
69
+ end
70
+
71
+ # Handle Ctrl-C interrupt
72
+ trap("INT") do
73
+ puts "Stopping..."
74
+
75
+ # Cancel the publisher timer
76
+ EM.cancel_timer(timer)
77
+
78
+ # Disconnect the producer/consumer connections
79
+ consumer.disconnect
80
+ producer.disconnect
81
+ duplex.disconnect
82
+
83
+ # Shutdown the EM loop
84
+ EM.add_timer(1) { EM.stop }
85
+ end
86
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "test_helper"
2
+
3
+ class DummyClient
4
+ include Messaging::Client
5
+ end
6
+
7
+ class ClientTest < MiniTest::Unit::TestCase
8
+ def setup
9
+ @client = DummyClient.new
10
+ @uri = "amqp://guest:guest@localhost:5672"
11
+ end
12
+
13
+ def test_open_connection_adds_retry_handlers
14
+ delay = 3
15
+
16
+ # Connection yield param
17
+ conn = mock()
18
+
19
+ # on_tcp_connection_loss handler sets periodically reconnect
20
+ on_tcp_loss = mock()
21
+ on_tcp_loss.expects(:periodically_reconnect).with(delay)
22
+ on_tcp_loss.expects(:to_ary)
23
+
24
+ # A retryable/recoverable error code
25
+ error = mock()
26
+ error.expects(:reply_code).returns(1)
27
+
28
+ # on_error handler sets periodically reconnect
29
+ on_error = mock()
30
+ on_error.expects(:periodically_reconnect).with(delay)
31
+
32
+ conn.expects(:on_tcp_connection_loss).yields(on_tcp_loss)
33
+ conn.expects(:on_error).yields(on_error, error)
34
+ conn.expects(:to_ary)
35
+
36
+ AMQP.stubs(:connect).yields(conn)
37
+
38
+ @client.open_connection(@uri, delay)
39
+ end
40
+
41
+ def test_open_channel_adds_recovery_handlers
42
+ prefetch = 16
43
+
44
+ # Channel yield param
45
+ chan = mock()
46
+ chan.expects(:on_error)
47
+ chan.expects(:auto_recovery=).with(true)
48
+ chan.expects(:prefetch).with(prefetch)
49
+ chan.expects(:id)
50
+
51
+ AMQP::Channel.stubs(:new).yields(chan, {})
52
+
53
+ @client.open_channel(mock(), prefetch)
54
+ end
55
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "test_helper"
2
+
3
+ class ConfigurationTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ Messaging::Configuration.instance_variable_set(
6
+ "@__instance__",
7
+ Messaging::Configuration.send(:new)
8
+ )
9
+
10
+ @config = Messaging::Configuration
11
+ end
12
+
13
+ def test_publish_to
14
+ assert(@config.instance.publish_to)
15
+
16
+ expected = "ballsacks"
17
+ @config.setup { |c| c.publish_to = expected }
18
+
19
+ assert_equal(expected, @config.instance.publish_to)
20
+ end
21
+
22
+ def test_consume_from
23
+ assert(@config.instance.consume_from.length > 0)
24
+
25
+ expected = "nutsacks"
26
+ @config.setup { |c| c.consume_from = expected }
27
+
28
+ assert_equal(expected, @config.instance.consume_from)
29
+ end
30
+
31
+ def test_prefetch
32
+ assert(@config.instance.prefetch > 0)
33
+
34
+ expected = 7
35
+ @config.setup { |c| c.prefetch = expected }
36
+
37
+ assert_equal(expected, @config.instance.prefetch)
38
+ end
39
+
40
+ def test_exchange_options
41
+ assert(@config.instance.exchange_options)
42
+
43
+ expected = { :nonsense => true }
44
+ @config.setup { |c| c.exchange_options = expected }
45
+
46
+ assert_equal(expected, @config.instance.exchange_options)
47
+ end
48
+
49
+ def test_queue_options
50
+ assert(@config.instance.queue_options)
51
+
52
+ expected = { :high_five => "ok!" }
53
+ @config.setup { |c| c.queue_options = expected }
54
+
55
+ assert_equal(expected, @config.instance.queue_options)
56
+ end
57
+
58
+ def test_reconnect_delay
59
+ assert(@config.instance.reconnect_delay > 0)
60
+
61
+ expected = 12
62
+ @config.setup { |c| c.reconnect_delay = expected }
63
+
64
+ assert_equal(expected, @config.instance.reconnect_delay)
65
+ end
66
+
67
+ def test_logger
68
+ assert(@config.instance.logger)
69
+
70
+ expected = "ballsacks"
71
+ @config.setup { |c| c.logger = expected }
72
+
73
+ assert_equal(expected, @config.instance.logger)
74
+ end
75
+ end
@@ -0,0 +1 @@
1
+ require_relative "test_helper"
@@ -0,0 +1,16 @@
1
+ require "minitest/autorun"
2
+ require "mocha"
3
+ require "messaging"
4
+ require "logger"
5
+
6
+ class MiniTestSetup < MiniTest::Unit
7
+ def _run_suites(suites, type)
8
+ Messaging::Configuration.setup do |config|
9
+ config.logger.level = Logger::Severity::UNKNOWN
10
+ end
11
+
12
+ super
13
+ end
14
+ end
15
+
16
+ MiniTest::Unit.runner = MiniTestSetup.new
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amqp-subscribe-many
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - brendanhay
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-10 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: amqp
16
+ requirement: &70180070452160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70180070452160
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &70180070451500 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70180070451500
36
+ - !ruby/object:Gem::Dependency
37
+ name: minitest
38
+ requirement: &70180070450860 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70180070450860
47
+ - !ruby/object:Gem::Dependency
48
+ name: mocha
49
+ requirement: &70180070450260 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70180070450260
58
+ - !ruby/object:Gem::Dependency
59
+ name: jeweler
60
+ requirement: &70180070449620 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70180070449620
69
+ - !ruby/object:Gem::Dependency
70
+ name: redcarpet
71
+ requirement: &70180070449020 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70180070449020
80
+ - !ruby/object:Gem::Dependency
81
+ name: yard
82
+ requirement: &70180070448440 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70180070448440
91
+ description: ! '''Publish-one, subscribe-many'' pattern implementation'
92
+ email: brendan@soundcloud.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files:
96
+ - LICENSE
97
+ - README.md
98
+ files:
99
+ - .travis.yml
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - LICENSE
103
+ - Makefile
104
+ - README.md
105
+ - Rakefile
106
+ - amqp-subscribe-many.gemspec
107
+ - lib/messaging.rb
108
+ - lib/messaging/client.rb
109
+ - lib/messaging/configuration.rb
110
+ - lib/messaging/consumer.rb
111
+ - lib/messaging/producer.rb
112
+ - script/config.yml
113
+ - script/run.rb
114
+ - test/client_test.rb
115
+ - test/configuration_test.rb
116
+ - test/consumer_test.rb
117
+ - test/test_helper.rb
118
+ homepage: http://github.com/brendanhay/amqp-subscribe-many
119
+ licenses:
120
+ - BSD
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ segments:
132
+ - 0
133
+ hash: 235469886921977757
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 1.8.7
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: ! '''Publish-one, subscribe-many'' pattern implementation'
146
+ test_files: []