msgr 0.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a4c0e2fbb4ebe739cd8441da5136c32974e33e3
4
+ data.tar.gz: 8cd8950010d87d4c3a4c90fa26a2f4544bfd1908
5
+ SHA512:
6
+ metadata.gz: 6b8268da9f156fa7be5b4da0768e766d90242d60186aa7a79d9d229270ac5957bf0e498c5edbb963bc8d0abb759e91545536e23444ff4ce6ab25bc8931518c96
7
+ data.tar.gz: 5af9d36176f5b99c4249c89a2819d4ab3682c90320d44a0c219b077ed2d3d4f7d9f6dd6e0899452d7d1c3b390ea89a2fdf53f9f2e41c91cb9cd58b55c24598db
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
19
+ pmip
20
+ *.iml
21
+ .rbx
22
+ .rspec
23
+ log/*.log
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ rvm:
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - jruby
7
+ - rbx-19mode
8
+
9
+ gemfile:
10
+ - gemfiles/Gemfile.rails-3-2
11
+ - gemfiles/Gemfile.rails-4-0
12
+
13
+ services:
14
+ - rabbitmq
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in msgr.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jan Graichen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Msgr - *A Rails-like Messaging Framework*
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/msgr.png)](http://badge.fury.io/rb/msgr)
4
+ [![Build Status](https://travis-ci.org/jgraichen/msgr.png?branch=master)](https://travis-ci.org/jgraichen/msgr)
5
+ [![Coverage Status](https://coveralls.io/repos/jgraichen/msgr/badge.png?branch=master)](https://coveralls.io/r/jgraichen/msgr)
6
+ [![Code Climate](https://codeclimate.com/github/jgraichen/msgr.png)](https://codeclimate.com/github/jgraichen/msgr)
7
+ [![Dependency Status](https://gemnasium.com/jgraichen/msgr.png)](https://gemnasium.com/jgraichen/msgr)
8
+ [![RubyDoc Documentation](https://raw.github.com/jgraichen/msgr/master/rubydoc.png)](http://rubydoc.info/github/jgraichen/msgr/master/frames)
9
+
10
+ You know it and ou like it. Using Rails you can just declare your routes and
11
+ create a controller. Now it just works.
12
+
13
+ With *Msgr* you can do the same for asynchronous AMQP messaging. Just define
14
+ your routes, create your consumer and watch you app processing messages.
15
+
16
+ *Note: Msgr is still under heavy development.*
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'msgr'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install msgr
31
+
32
+ ## Usage
33
+
34
+ TODO: Write usage instructions here
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rake'
8
+ require 'bundler/gem_tasks'
9
+ require 'rspec/core/rake_task'
10
+
11
+ task 'default' => 'ci'
12
+ task 'ci' => 'spec'
13
+
14
+ desc 'Run all specs'
15
+ RSpec::Core::RakeTask.new('spec') do |t|
16
+ t.pattern = 'spec/msgr/**/*_spec.rb'
17
+ end
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Load root Gemfile
4
+ self.instance_eval Bundler.read_file 'Gemfile'
5
+
6
+ gem 'activesupport', '~> 3.2.0'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Load root Gemfile
4
+ self.instance_eval Bundler.read_file 'Gemfile'
5
+
6
+ gem 'activesupport', '~> 4.0.0'
data/lib/msgr.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'msgr/version'
2
+ require 'celluloid'
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/module/delegation'
6
+ require 'active_support/core_ext/string/inflections'
7
+
8
+ require 'msgr/logging'
9
+ require 'msgr/binding'
10
+ require 'msgr/client'
11
+ require 'msgr/connection'
12
+ require 'msgr/dispatcher'
13
+ require 'msgr/errors'
14
+ require 'msgr/message'
15
+ require 'msgr/pool'
16
+ require 'msgr/route'
17
+ require 'msgr/routes'
18
+
19
+ module Msgr
20
+
21
+ class << self
22
+ def logger
23
+ @logger ||= Logger.new($stdout).tap do |logger|
24
+ logger.level = Logger::Severity::INFO
25
+ end
26
+ end
27
+
28
+ def start
29
+ # stub
30
+ end
31
+
32
+ def publish
33
+ # stub
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ module Msgr
2
+ # A single binding
3
+ class Binding
4
+ attr_reader :connection, :route, :subscription, :dispatcher
5
+
6
+ def initialize(connection, route, dispatcher)
7
+ @connection = connection
8
+ @route = route
9
+ @dispatcher = dispatcher
10
+
11
+ queue = connection.queue route.name
12
+
13
+ queue.bind connection.exchange, routing_key: route.key
14
+
15
+ @subscription = queue.subscribe ack: true, &method(:call)
16
+ end
17
+
18
+ # Called from Bunny Thread Pool. Will create message object from
19
+ # provided bunny data and dispatch message to connection.
20
+ #
21
+ def call(info, metadata, payload)
22
+ message = Message.new(connection, info, metadata, payload, route)
23
+ dispatcher.dispatch :call, message
24
+ rescue => error
25
+ Msgr.logger.warn(self) { "Error received within bunny subscribe handler: #{error.inspect}." }
26
+ end
27
+
28
+ # Cancel subscription to not receive any more messages.
29
+ #
30
+ def release
31
+ subscription.cancel if subscription
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,82 @@
1
+ require 'bunny'
2
+
3
+ module Msgr
4
+
5
+ class Client
6
+ include Celluloid
7
+ include Logging
8
+
9
+ attr_reader :pool, :uri
10
+
11
+ def initialize(config)
12
+ @uri = URI.parse config[:uri] ? config.delete(:uri) : 'amqp://localhost/'
13
+ @uri.protocol = 'amqps' if config[:secure]
14
+ @uri.user = config.delete :user if config[:user]
15
+ @uri.password = config.delete :password if config[:password]
16
+ @uri.host = config.delete :host if config[:host]
17
+ @uri.port = config.delete(:port).to_i if config[:port]
18
+ @uri.path = "/#{config.delete :vhost}".gsub /\/+/, '/' if config[:vhost]
19
+
20
+ @config = config
21
+ @bunny = Bunny.new @uri.to_s
22
+ @pool = Pool.new Dispatcher, autostart: false
23
+
24
+ @uri.password = nil
25
+ end
26
+
27
+ def running?; @running end
28
+ def log_name; self.class.name end
29
+
30
+ def routes
31
+ @routes ||= Routes.new
32
+ end
33
+
34
+ def reload
35
+ raise StandardError.new 'Client not running.' unless running?
36
+ log(:info) { 'Reload client.' }
37
+
38
+ @connection.release
39
+ @connection.terminate
40
+
41
+ log(:debug) { 'Create new connection.' }
42
+
43
+ @connection = Connection.new @bunny, routes, pool
44
+
45
+ log(:info) { 'Client reloaded.' }
46
+ end
47
+
48
+ def start
49
+ log(:info) { "Start client to #{uri}" }
50
+
51
+ @bunny.start
52
+ @pool.start
53
+
54
+ @running = true
55
+ @connection = Connection.new @bunny, routes, pool
56
+
57
+ log(:info) { "Client started. pool: #{pool.size}" }
58
+ end
59
+
60
+ def stop
61
+ return unless running?
62
+
63
+ @running = false
64
+ log(:info) { 'Graceful shutdown client...' }
65
+
66
+ @connection.release
67
+ @pool.stop
68
+
69
+ log(:debug) { 'Terminating...' }
70
+
71
+ @connection.terminate
72
+ @pool.terminate
73
+ @bunny.stop
74
+
75
+ log(:info) { 'Terminated.' }
76
+ end
77
+
78
+ def publish(routing_key, payload)
79
+ @connection.publish payload, routing_key: routing_key
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,78 @@
1
+ module Msgr
2
+
3
+ class Connection
4
+ include Celluloid
5
+ include Logging
6
+
7
+ attr_reader :conn, :pool, :routes
8
+ finalizer :close
9
+
10
+ def initialize(conn, routes, pool)
11
+ @conn = conn
12
+ @pool = pool
13
+ @routes = routes
14
+
15
+ @channel = conn.create_channel
16
+ @channel.prefetch(10)
17
+
18
+ rebind
19
+ end
20
+
21
+ def rebind(routes = nil)
22
+ routes = self.routes unless routes
23
+
24
+ # First release old bindings
25
+ release
26
+
27
+ # Create new bindings
28
+ routes.each { |route| bindings << Binding.new(Actor.current, route, pool) }
29
+
30
+ log(:debug) { 'New routes bound.' }
31
+ end
32
+
33
+ # Used to store al bindings. Allows use to
34
+ # release bindings when receiver should not longer
35
+ # receive messages but channel need to be open
36
+ # to allow further acknowledgments.
37
+ #
38
+ def bindings
39
+ @bindings ||= []
40
+ end
41
+
42
+ def queue(name)
43
+ @channel.queue name, durable: true
44
+ end
45
+
46
+ def exchange
47
+ @exchange ||= @channel.topic 'msgr', durable: true
48
+ end
49
+
50
+ # Release all bindings but do not close channel. Will not
51
+ # longer receive any message but channel can be used to
52
+ # acknowledge currently processing messages.
53
+ #
54
+ def release
55
+ return unless bindings.any?
56
+
57
+ log(:debug) { 'Release all bindings.' }
58
+
59
+ bindings.each { |binding| binding.release }
60
+ bindings.clear
61
+ end
62
+
63
+ def publish(payload, opts = {})
64
+ log(:debug) { "Publish message to #{opts[:routing_key]}" }
65
+
66
+ exchange.publish payload, opts.merge(persistent: true)
67
+ end
68
+
69
+ def ack(delivery_tag)
70
+ @channel.ack delivery_tag
71
+ end
72
+
73
+ def close
74
+ @channel.close if @channel.open?
75
+ log(:debug) { 'Connection closed.' }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,26 @@
1
+ module Msgr
2
+
3
+ # The Dispatcher receives incoming messages,
4
+ # process them through a middleware stack and
5
+ # delegate them to a new and fresh consumer instance.
6
+ #
7
+ class Dispatcher
8
+ include Logging
9
+
10
+ def initialize
11
+
12
+ end
13
+
14
+ def call(message)
15
+ log(:debug) { "Receive dispatched message: #{message.payload}" }
16
+
17
+ message.ack
18
+
19
+ log(:debug) { 'Dispatched message acknowledged.' }
20
+ end
21
+
22
+ def to_s
23
+ self.class.name
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Msgr
2
+
3
+ # Abstract error base class
4
+ class CausedByError < StandardError
5
+ attr_accessor :cause
6
+
7
+ def initialize(*args)
8
+ opts = args.extract_options!
9
+ @cause = opts.delete(:cause)
10
+ super
11
+ end
12
+
13
+ def message
14
+ cause ? "#{super}\n caused by:\n#{cause.to_s}" : super
15
+ end
16
+ end
17
+
18
+ class ConnectionError < CausedByError
19
+
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module Msgr
2
+
3
+ module Logging
4
+ def log(level)
5
+ Msgr.logger.send(level, self.log_name) { yield }
6
+ end
7
+
8
+ def log_name
9
+ "[#{Thread.current.object_id}] #{self.to_s}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Msgr
2
+
3
+ class Message
4
+ attr_reader :delivery_info, :metadata, :payload
5
+
6
+ def initialize(connection, delivery_info, metadata, payload, route)
7
+ @connection = connection
8
+ @delivery_info = delivery_info
9
+ @metadata = metadata
10
+ @payload = payload
11
+ @route = route
12
+ end
13
+
14
+ def ack
15
+ @connection.ack delivery_info.delivery_tag
16
+ end
17
+ end
18
+ end
data/lib/msgr/pool.rb ADDED
@@ -0,0 +1,165 @@
1
+ module Msgr
2
+
3
+ class Pool
4
+ include Celluloid
5
+ include Logging
6
+ attr_reader :size
7
+
8
+ def initialize(runner_klass, opts = {})
9
+ @runner_klass = runner_klass
10
+ @runner_args = opts[:args] ? Array(opts[:args]) : []
11
+ @size = (opts[:size] || Celluloid.cores).to_i
12
+ @running = false
13
+
14
+ log(:debug) { "Inialize size => #{@size}" }
15
+
16
+ start if opts[:autostart].nil? || opts[:autostart]
17
+ every(30) { log_status } if opts[:nostats].nil? || opts[:nostats]
18
+ end
19
+
20
+ def running?
21
+ @running
22
+ end
23
+
24
+ def idle; @idle ||= [] end
25
+ def busy; @busy ||= [] end
26
+
27
+ def start
28
+ return if running?
29
+
30
+ log(:debug) { 'Spin up worker pool' }
31
+ @running = true
32
+
33
+ idle.clear
34
+ busy.clear
35
+
36
+ @size.times.map do |index|
37
+ idle << Worker.new_link(Actor.current, index, @runner_klass, @runner_args)
38
+ end
39
+
40
+ log(:debug) { 'Startup done. Invoke worker polling.' }
41
+
42
+ idle.each { |worker| async.poll worker }
43
+ end
44
+
45
+ def log_status
46
+ log(:info) { "[STATUS] Idle: #{idle.size} Busy: #{busy.size}" }
47
+ end
48
+
49
+ # Request a graceful shutdown of all pool workers.
50
+ #
51
+ def stop
52
+ log(:debug) { 'Graceful shutdown requested.' }
53
+
54
+ @running = false
55
+ idle.each { |worker| worker.terminate }
56
+ idle.clear
57
+
58
+ if busy.any?
59
+ log(:debug) { "Wait for #{busy.size} workers to terminate." }
60
+
61
+ wait :shutdown
62
+ end
63
+
64
+ log(:debug) { 'Graceful shutdown done.' }
65
+ end
66
+
67
+ # Check if a worker is available.
68
+ #
69
+ # @return [Boolean] True if at least on idle worker is available, false otherwise.
70
+ #
71
+ def available?
72
+ idle.any?
73
+ end
74
+
75
+ def messages
76
+ @message ||= []
77
+ end
78
+
79
+ # Dispatch given message to a worker.
80
+ #
81
+ def dispatch(message, *args)
82
+ messages.push [message, args]
83
+ after(0) { signal :dispatch }
84
+ end
85
+
86
+ # Called by worker to indicated it has finished processing.
87
+ #
88
+ # @param [Pool::Worker] worker Worker that finished processing.
89
+ #
90
+ def executed(worker)
91
+ busy.delete worker
92
+
93
+ if running?
94
+ idle << worker
95
+ poll worker
96
+ else
97
+ log(:debug) { "Terminate worker. Still #{busy.size} to go..." }
98
+
99
+ worker.terminate if worker.alive?
100
+ if busy.empty?
101
+ log(:debug) { 'All worker down. Signal :shutdown.' }
102
+ after(0) { signal :shutdown }
103
+ end
104
+ end
105
+ end
106
+
107
+ def poll(worker)
108
+ return unless worker.alive?
109
+
110
+ if running?
111
+ if (message = exclusive { messages.shift })
112
+ idle.delete worker
113
+ busy << worker
114
+
115
+ worker.dispatch message[0], message[1]
116
+ else
117
+ after(1) { poll worker }
118
+ end
119
+ else
120
+ worker.terminate if worker.alive?
121
+ after(0) { signal(:shutdown) } if @busy.empty?
122
+ end
123
+ end
124
+
125
+ def to_s
126
+ "#{self.class.name}[#{@runner_klass}]<#{object_id}>"
127
+ end
128
+
129
+ # Worker actor capsuling worker logic and dispatching
130
+ # tasks to custom runner object.
131
+ #
132
+ class Worker
133
+ include Celluloid
134
+ include Logging
135
+ attr_reader :pool, :index, :runner
136
+
137
+ def initialize(pool, index, runner_klass, runner_args)
138
+ @pool = pool
139
+ @poolname = pool.to_s
140
+ @index = index
141
+ @runner = runner_klass.new *runner_args
142
+
143
+ log(:debug) { 'Worker ready.' }
144
+ end
145
+
146
+ # Dispatch given method and argument to custom runner.
147
+ # Arguments are used to call `#send` on runner instance.
148
+ #
149
+ def dispatch(method, args)
150
+ log(:debug) { "Dispatch to runner: #{runner.class.name}##{method.to_s}" }
151
+
152
+ # Send method to custom runner.
153
+ runner.send method, *args
154
+ rescue => error
155
+ log(:error) { "Received error from runner: #{error.message}\n#{error.backtrace.join(" \n")}" }
156
+ ensure
157
+ pool.executed Actor.current
158
+ end
159
+
160
+ def to_s
161
+ "#{@poolname}[##{index}]"
162
+ end
163
+ end
164
+ end
165
+ end
data/lib/msgr/route.rb ADDED
@@ -0,0 +1,26 @@
1
+ module Msgr
2
+
3
+ class Route
4
+ attr_reader :consumer, :action, :opts, :key
5
+ alias_method :routing_key, :key
6
+
7
+ def initialize(key, opts = {})
8
+ @key = key.to_s
9
+ @opts = opts
10
+
11
+ raise ArgumentError.new 'Routing key required.' unless @key.present?
12
+ raise ArgumentError.new 'Missing `to` options.' unless @opts[:to]
13
+
14
+ if (match = /\A(?<consumer>\w+)#(?<action>\w+)\z/.match(opts[:to].strip.to_s))
15
+ @consumer = "#{match[:consumer].camelize}Consumer"
16
+ @action = match[:action].underscore
17
+ else
18
+ raise ArgumentError.new "Invalid consumer format: #{opts[:to].strip.to_s.inspect}. Must be `consumer_class#action`."
19
+ end
20
+ end
21
+
22
+ def name
23
+ "msgr.consumer-#{key}//#{consumer}##{action}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module Msgr
2
+
3
+ class Routes
4
+ delegate :each, to: :@routes
5
+
6
+ def routes
7
+ @routes ||= []
8
+ end
9
+
10
+ def configure(&block)
11
+ instance_eval &block
12
+ end
13
+
14
+ def route(key, opts = {})
15
+ routes << Msgr::Route.new(key, opts)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module Msgr
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ PATCH = 0
6
+ STAGE = nil
7
+ STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
8
+
9
+ def self.to_s; STRING end
10
+ end
11
+ end
data/msgr.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'msgr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'msgr'
8
+ spec.version = Msgr::VERSION
9
+ spec.authors = ['Jan Graichen']
10
+ spec.email = ['jg@altimos.de']
11
+ spec.description = %q{}
12
+ spec.summary = %q{}
13
+ spec.homepage = 'https://github.com/jgraichen/msgr'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activesupport'
22
+ spec.add_dependency 'bunny', '~> 0.10.0'
23
+ spec.add_dependency 'celluloid'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'coveralls'
29
+ end
@@ -0,0 +1,114 @@
1
+ require 'msgr'
2
+
3
+ Msgr.logger.level = Logger::Severity::DEBUG
4
+
5
+ @client = Msgr::Client.new uri: 'amqp://msgr:msgr@localhost'
6
+
7
+ @client.routes.configure do
8
+ route 'abc.#', to: 'test#index'
9
+ route 'cde.#', to: 'test#index'
10
+ route '#', to: 'test#another_action'
11
+ end
12
+
13
+ @client.start
14
+
15
+ 10.times do |i|
16
+ @client.publish 'abc.XXX', "Message #{i} #{rand}"
17
+ end
18
+
19
+ sleep 5
20
+
21
+ @client.routes.configure do
22
+ route 'abc.#', to: 'test#index'
23
+ end
24
+
25
+ @client.reload
26
+
27
+ 10.times do |i|
28
+ @client.publish 'abc.XXX', "Message #{i} #{rand}"
29
+ end
30
+
31
+ begin
32
+ sleep
33
+ rescue Interrupt
34
+ @client.stop
35
+ end
36
+
37
+ #class Dispatcher
38
+ # include Msgr::Logging
39
+ #
40
+ # def call(message)
41
+ # log(:info) { message }
42
+ # sleep 5 * rand
43
+ # log(:info) { 'Done' }
44
+ # end
45
+ #end
46
+ #
47
+ #pool = Msgr::Pool.new Dispatcher, size: 10
48
+ #pool.start
49
+ #
50
+ #100.times do |i|
51
+ # pool.dispatch(:call, "Message ##{i}")
52
+ #end
53
+ #
54
+ #sleep 5
55
+ #
56
+ #pool.stop
57
+ #pool.terminate
58
+ #
59
+ #Msgr.logger.info('[ROOT]') { 'Pool terminated.' }
60
+
61
+ #require 'celluloid'
62
+ #
63
+ #class Worker
64
+ # include Celluloid
65
+ #
66
+ # def do_work
67
+ # sleep 15
68
+ # end
69
+ #end
70
+ #
71
+ #logger = Logger.new $stdout
72
+ #
73
+ #pool = Worker.pool
74
+ #
75
+ #logger.info 'Start work'
76
+ #
77
+ #4.times do |i|
78
+ # pool.async.do_work
79
+ #end
80
+ #
81
+ #logger.info 'Wait'
82
+ #
83
+ #sleep 5
84
+ #
85
+ #logger.info 'Terminate'
86
+ #
87
+ #pool.terminate
88
+ #
89
+ #logger.info 'Done.'
90
+
91
+
92
+ #require 'bunny'
93
+ #
94
+ #bunny = Bunny.new 'amqp://msgr:msgr@localhost'
95
+ #bunny.start
96
+ #
97
+ #channel = bunny.create_channel
98
+ #exchange = channel.topic 'msgr.topic'
99
+ #queue = channel.queue 'msgr.test.single-queue'
100
+ #
101
+ #queue.bind(exchange, routing_key: 'a.b.#')
102
+ #queue.bind(exchange, routing_key: 'a.c.#')
103
+ #queue.subscribe do |delivery_info, metadata, payload|
104
+ # puts "#{delivery_info.routing_key} #{payload}"
105
+ #end
106
+ #
107
+ #sleep 1
108
+ #
109
+ #10.times { |i| exchange.publish "Message ##{i}", routing_key: [ 'a.b.c', 'a.c.d', 'a.b', 'a' ].sample; sleep 0.2 }
110
+ #
111
+ #sleep 10
112
+ #
113
+ #channel.close
114
+ #bunny.close
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Msgr::Client do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ # describe Msgr::Consumer do
4
+ #
5
+ # end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Msgr::Route do
4
+ let(:routing_key) { 'routing.key.#' }
5
+ let(:options) { {to: 'test#index'} }
6
+ let(:args) { [routing_key, options] }
7
+ let(:route) { Msgr::Route.new *args }
8
+ subject { route }
9
+
10
+ describe '#initialize' do
11
+ it 'should require `to` option' do
12
+ expect {
13
+ Msgr::Route.new(routing_key, {})
14
+ }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'should require routing_key' do
18
+ expect {
19
+ Msgr::Route.new nil, options
20
+ }.to raise_error(ArgumentError)
21
+ end
22
+
23
+ it 'should require not empty routing_key' do
24
+ expect {
25
+ Msgr::Route.new '', options
26
+ }.to raise_error ArgumentError, /routing key required/i
27
+ end
28
+
29
+ it 'should require `to: "consumer#action` format' do
30
+ expect {
31
+ Msgr::Route.new routing_key, to: 'abc'
32
+ }.to raise_error ArgumentError, /invalid consumer format/i
33
+ end
34
+ end
35
+
36
+ describe '#consumer' do
37
+ it 'should return consumer class name' do
38
+ expect(route.consumer).to eq 'TestConsumer'
39
+ end
40
+
41
+ context 'with underscore consumer name' do
42
+ let(:options) { super().merge to: 'test_resource_foo#index' }
43
+
44
+ it 'should return camelized method name' do
45
+ expect(route.consumer).to eq 'TestResourceFooConsumer'
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#action' do
51
+ it 'should return action method name' do
52
+ expect(route.action).to eq 'index'
53
+ end
54
+
55
+ context 'with camelCase action name' do
56
+ let(:options) { super().merge to: 'test#myActionMethod' }
57
+
58
+ it 'should return underscore method name' do
59
+ expect(route.action).to eq 'my_action_method'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Msgr::Routes do
4
+ let(:routes) { Msgr::Routes.new }
5
+
6
+ describe '#configure' do
7
+ let(:block) { Proc.new{} }
8
+
9
+ it 'should evaluate given block within instance context' do
10
+ expect(routes).to receive(:instance_eval).with { |p| p == block }
11
+
12
+ routes.configure &block
13
+ end
14
+
15
+ it 'should allow to call instance method in gven block' do
16
+ expect(routes).to receive(:test_instance_method).with(:abc)
17
+
18
+ routes.configure do
19
+ test_instance_method :abc
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#each' do
25
+ before do
26
+ routes.configure do
27
+ route 'abc.#', to: 'test#index'
28
+ route 'edf.#', to: 'test#index'
29
+ end
30
+ end
31
+
32
+ let(:each) { routes.each }
33
+
34
+ it 'should iterate over configured routes' do
35
+ expect(each).to have(2).items
36
+
37
+ expect(each.map(&:routing_key)).to be == %w(abc.# edf.#)
38
+ expect(each.map(&:consumer)).to be == %w(TestConsumer TestConsumer)
39
+ expect(each.map(&:action)).to be == %w(index index)
40
+ end
41
+ end
42
+
43
+ describe '#route' do
44
+ let(:subject) { -> { routes.route 'routing.key', to: 'test2#index2' } }
45
+ let(:last_route) { routes.routes.last }
46
+
47
+ it 'should add a new route' do
48
+ expect { subject.call }.to change{ routes.routes.size }.from(0).to(1)
49
+ end
50
+
51
+ it 'should add given route' do
52
+ subject.call
53
+
54
+ expect(last_route.routing_key).to eq 'routing.key'
55
+ expect(last_route.consumer).to eq 'Test2Consumer'
56
+ expect(last_route.action).to eq 'index2'
57
+ end
58
+ end
59
+ end
data/spec/msgr_spec.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Msgr do
4
+
5
+ end
@@ -0,0 +1,18 @@
1
+ # Coverage
2
+ require 'coveralls'
3
+ Coveralls.wear! do
4
+ add_filter 'spec'
5
+ end
6
+
7
+ require 'rspec/autorun'
8
+ require 'msgr'
9
+
10
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
11
+
12
+ RSpec.configure do |config|
13
+ config.order = 'random'
14
+ config.expect_with :rspec do |c|
15
+ # Only allow expect syntax
16
+ c.syntax = :expect
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: msgr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Graichen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bunny
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.10.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: celluloid
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: ''
112
+ email:
113
+ - jg@altimos.de
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - gemfiles/Gemfile.rails-3-2
125
+ - gemfiles/Gemfile.rails-4-0
126
+ - lib/msgr.rb
127
+ - lib/msgr/binding.rb
128
+ - lib/msgr/client.rb
129
+ - lib/msgr/connection.rb
130
+ - lib/msgr/dispatcher.rb
131
+ - lib/msgr/errors.rb
132
+ - lib/msgr/logging.rb
133
+ - lib/msgr/message.rb
134
+ - lib/msgr/pool.rb
135
+ - lib/msgr/route.rb
136
+ - lib/msgr/routes.rb
137
+ - lib/msgr/version.rb
138
+ - msgr.gemspec
139
+ - scripts/simple_test.rb
140
+ - spec/msgr/client_spec.rb
141
+ - spec/msgr/consumer_spec.rb
142
+ - spec/msgr/route_spec.rb
143
+ - spec/msgr/routes_spec.rb
144
+ - spec/msgr_spec.rb
145
+ - spec/spec_helper.rb
146
+ homepage: https://github.com/jgraichen/msgr
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.0.6
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: ''
170
+ test_files:
171
+ - spec/msgr/client_spec.rb
172
+ - spec/msgr/consumer_spec.rb
173
+ - spec/msgr/route_spec.rb
174
+ - spec/msgr/routes_spec.rb
175
+ - spec/msgr_spec.rb
176
+ - spec/spec_helper.rb