msgr 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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