msgr 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +7 -1
- data/.travis.yml +5 -3
- data/Gemfile +27 -2
- data/Guardfile +14 -0
- data/README.md +56 -5
- data/Rakefile +30 -4
- data/lib/msgr/binding.rb +34 -7
- data/lib/msgr/client.rb +54 -24
- data/lib/msgr/connection.rb +64 -17
- data/lib/msgr/consumer.rb +28 -0
- data/lib/msgr/dispatcher.rb +12 -5
- data/lib/msgr/logging.rb +1 -1
- data/lib/msgr/message/acknowledge.rb +30 -0
- data/lib/msgr/message.rb +10 -3
- data/lib/msgr/pool.rb +31 -31
- data/lib/msgr/railtie.rb +51 -0
- data/lib/msgr/route.rb +19 -6
- data/lib/msgr/routes.rb +34 -2
- data/lib/msgr/version.rb +1 -1
- data/lib/msgr.rb +15 -7
- data/msgr.gemspec +2 -5
- data/scripts/simple_test.rb +18 -94
- data/spec/fixtures/msgr-routes-test-1.rb +2 -0
- data/spec/integration/dummy/README.rdoc +28 -0
- data/spec/integration/dummy/Rakefile +6 -0
- data/spec/integration/dummy/app/assets/images/.keep +0 -0
- data/spec/integration/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/integration/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/integration/dummy/app/consumers/application_consumer.rb +3 -0
- data/spec/integration/dummy/app/consumers/test_consumer.rb +12 -0
- data/spec/integration/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/integration/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/integration/dummy/app/controllers/test_controller.rb +10 -0
- data/spec/integration/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/integration/dummy/app/mailers/.keep +0 -0
- data/spec/integration/dummy/app/models/.keep +0 -0
- data/spec/integration/dummy/app/models/concerns/.keep +0 -0
- data/spec/integration/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/integration/dummy/bin/bundle +3 -0
- data/spec/integration/dummy/bin/rails +4 -0
- data/spec/integration/dummy/bin/rake +4 -0
- data/spec/integration/dummy/config/application.rb +24 -0
- data/spec/integration/dummy/config/boot.rb +5 -0
- data/spec/integration/dummy/config/database.yml +25 -0
- data/spec/integration/dummy/config/environment.rb +5 -0
- data/spec/integration/dummy/config/environments/development.rb +29 -0
- data/spec/integration/dummy/config/environments/production.rb +80 -0
- data/spec/integration/dummy/config/environments/test.rb +36 -0
- data/spec/integration/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/integration/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/integration/dummy/config/initializers/inflections.rb +16 -0
- data/spec/integration/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/integration/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/integration/dummy/config/initializers/session_store.rb +3 -0
- data/spec/integration/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/integration/dummy/config/locales/en.yml +23 -0
- data/spec/integration/dummy/config/msgr.rb +3 -0
- data/spec/integration/dummy/config/rabbitmq.yml +11 -0
- data/spec/integration/dummy/config/routes.rb +58 -0
- data/spec/integration/dummy/config.ru +4 -0
- data/spec/integration/dummy/db/test.sqlite3 +0 -0
- data/spec/integration/dummy/lib/assets/.keep +0 -0
- data/spec/integration/dummy/log/.keep +0 -0
- data/spec/integration/dummy/public/404.html +58 -0
- data/spec/integration/dummy/public/422.html +58 -0
- data/spec/integration/dummy/public/500.html +57 -0
- data/spec/integration/dummy/public/favicon.ico +0 -0
- data/spec/integration/msgr/railtie_spec.rb +12 -0
- data/spec/integration/msgr_spec.rb +12 -0
- data/spec/integration/spec_helper.rb +48 -0
- data/spec/msgr/msgr/client_spec.rb +50 -0
- data/spec/msgr/msgr/connection_spec.rb +51 -0
- data/spec/msgr/{consumer_spec.rb → msgr/consumer_spec.rb} +0 -0
- data/spec/msgr/msgr/pool_spec.rb +68 -0
- data/spec/msgr/{route_spec.rb → msgr/route_spec.rb} +0 -0
- data/spec/msgr/msgr/routes_spec.rb +113 -0
- data/spec/msgr/msgr_spec.rb +36 -0
- data/spec/{spec_helper.rb → msgr/spec_helper.rb} +15 -0
- data/spec/msgr/support/.keep +0 -0
- data/spec/support/setup.rb +27 -0
- metadata +126 -59
- data/spec/msgr/client_spec.rb +0 -5
- data/spec/msgr/routes_spec.rb +0 -59
- data/spec/msgr_spec.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8d6c4cc8a9208899a92286b5ee2be798aceb0bd
|
4
|
+
data.tar.gz: 5c3c2a20221a8da75f8aa4ab81e0e792daf15ab3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48370c17631a999f1bf475ec869944d95868486d68d7e28cc3020947fa064592c8d0f98c5072acc285af13cad4624524ca66616af5ecadf964ce7bca7da73eac
|
7
|
+
data.tar.gz: 35bf1311b013e5a5a15ec733a0daa343b83bca70b2fd7b49700b4988afc94c233cec0fd25a7afbf324dc633ed300e4580cf8b3494a622c38fff98aa8856e449a
|
data/.gitignore
CHANGED
@@ -20,4 +20,10 @@ pmip
|
|
20
20
|
*.iml
|
21
21
|
.rbx
|
22
22
|
.rspec
|
23
|
-
log
|
23
|
+
*.log
|
24
|
+
spec/integration/dummy/db/*.sqlite3
|
25
|
+
spec/integration/dummy/db/*.sqlite3-journal
|
26
|
+
spec/integration/dummy/tmp
|
27
|
+
spec/integration/dummy/.sass-cache
|
28
|
+
spec/integration/dummy/.generators
|
29
|
+
spec/integration/dummy/.rakeTasks
|
data/.travis.yml
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
language: ruby
|
2
2
|
bundler_args: --without development
|
3
|
+
services:
|
4
|
+
- rabbitmq
|
3
5
|
rvm:
|
4
6
|
- 2.0.0
|
5
7
|
- 1.9.3
|
6
8
|
- jruby
|
7
9
|
- rbx-19mode
|
8
|
-
|
9
10
|
gemfile:
|
10
11
|
- gemfiles/Gemfile.rails-3-2
|
11
12
|
- gemfiles/Gemfile.rails-4-0
|
12
13
|
|
13
|
-
|
14
|
-
-
|
14
|
+
script:
|
15
|
+
- bundle exec rake spec:msgr
|
16
|
+
- bundle exec rake spec:integration
|
data/Gemfile
CHANGED
@@ -1,4 +1,29 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
#
|
4
|
-
|
3
|
+
# Development gems
|
4
|
+
#
|
5
|
+
gem 'rake'
|
6
|
+
gem 'rspec'
|
7
|
+
gem 'rspec-message-within', '>= 1.1.1'
|
8
|
+
gem 'fuubar'
|
9
|
+
gem 'coveralls'
|
10
|
+
|
11
|
+
# Doc
|
12
|
+
group :development do
|
13
|
+
gem 'yard', '~> 0.8.6'
|
14
|
+
gem 'listen'
|
15
|
+
gem 'guard-yard'
|
16
|
+
gem 'guard-rspec'
|
17
|
+
gem 'redcarpet', platform: :ruby
|
18
|
+
end
|
19
|
+
|
20
|
+
group :rails do
|
21
|
+
gem 'rails'
|
22
|
+
gem 'rspec-rails'
|
23
|
+
gem 'sqlite3', platform: :ruby
|
24
|
+
gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify your gem's dependencies in acfs.gemspec
|
28
|
+
gemroot = File.dirname File.absolute_path __FILE__
|
29
|
+
gemspec path: gemroot
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
|
8
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
9
|
+
watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
|
10
|
+
end
|
11
|
+
|
12
|
+
guard 'yard' do
|
13
|
+
watch(%r{lib/.+\.rb})
|
14
|
+
end
|
data/README.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
# Msgr
|
1
|
+
# Msgr: *Rails-like Messaging Framework*
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/msgr.png)](http://badge.fury.io/rb/msgr)
|
4
4
|
[![Build Status](https://travis-ci.org/jgraichen/msgr.png?branch=master)](https://travis-ci.org/jgraichen/msgr)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/jgraichen/msgr/badge.png?branch=master)](https://coveralls.io/r/jgraichen/msgr)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/jgraichen/msgr.png)](https://codeclimate.com/github/jgraichen/msgr)
|
7
7
|
[![Dependency Status](https://gemnasium.com/jgraichen/msgr.png)](https://gemnasium.com/jgraichen/msgr)
|
8
|
-
[![RubyDoc Documentation](https://raw.github.com/jgraichen/
|
8
|
+
[![RubyDoc Documentation](https://raw.github.com/jgraichen/shields/master/rubydoc.png)](http://rubydoc.info/github/jgraichen/msgr/master/frames)
|
9
9
|
|
10
|
-
You know it and
|
11
|
-
create a controller.
|
10
|
+
You know it and you like it. Using Rails you can just declare your routes and
|
11
|
+
create a controller. That's all you need to process requests.
|
12
12
|
|
13
13
|
With *Msgr* you can do the same for asynchronous AMQP messaging. Just define
|
14
14
|
your routes, create your consumer and watch you app processing messages.
|
@@ -31,7 +31,58 @@ Or install it yourself as:
|
|
31
31
|
|
32
32
|
## Usage
|
33
33
|
|
34
|
-
|
34
|
+
After adding 'msgr' to your gemfile create a `config/rabbitmq.yml` like this:
|
35
|
+
|
36
|
+
```yaml
|
37
|
+
common: &common
|
38
|
+
uri: amqp://localhost/
|
39
|
+
|
40
|
+
test:
|
41
|
+
<<: *common
|
42
|
+
|
43
|
+
development:
|
44
|
+
<<: *common
|
45
|
+
|
46
|
+
production:
|
47
|
+
<<: *common
|
48
|
+
```
|
49
|
+
|
50
|
+
Specify your messaging routes in `config/msgr.rb`:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
route 'local.test.index', to: 'test#index'
|
54
|
+
route 'local.test.another_action', to: 'test#another_action'
|
55
|
+
```
|
56
|
+
|
57
|
+
Create your consumer in `app/consumers`:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class TestConsumer < ApplicationConsumer
|
61
|
+
def index
|
62
|
+
data = { fuubar: 'abc' }
|
63
|
+
|
64
|
+
publish data, to: 'local.test.another_action'
|
65
|
+
end
|
66
|
+
|
67
|
+
def another_action
|
68
|
+
puts "#{payload.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
Use `Msgr.publish` in to publish a message:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class TestController < ApplicationController
|
77
|
+
def index
|
78
|
+
@data = { abc: 'abc' }
|
79
|
+
|
80
|
+
Msgr.publish @data, to: 'local.test.index'
|
81
|
+
|
82
|
+
render nothing: true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
35
86
|
|
36
87
|
## Contributing
|
37
88
|
|
data/Rakefile
CHANGED
@@ -8,10 +8,36 @@ require 'rake'
|
|
8
8
|
require 'bundler/gem_tasks'
|
9
9
|
require 'rspec/core/rake_task'
|
10
10
|
|
11
|
-
task
|
12
|
-
task
|
11
|
+
# Delegate spec task task to spec:all to run all specs.
|
12
|
+
task :spec => 'spec:all'
|
13
13
|
|
14
14
|
desc 'Run all specs'
|
15
|
-
|
16
|
-
|
15
|
+
namespace :spec do
|
16
|
+
|
17
|
+
desc 'Run all msgr specs and all integration specs.'
|
18
|
+
task :all => [ :msgr, :integration ]
|
19
|
+
|
20
|
+
desc 'Run all unit specs.'
|
21
|
+
RSpec::Core::RakeTask.new(:msgr) do |t|
|
22
|
+
t.ruby_opts="#{ENV['CI'] ? '' : '-w -W2'} -Ispec/support -rsetup -Ispec/msgr"
|
23
|
+
t.pattern = 'spec/msgr/**/*_spec.rb'
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Run all integration specs.'
|
27
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
28
|
+
t.ruby_opts="#{ENV['CI'] ? '' : '-w -W2'} -Ispec/support -rsetup -Ispec/integration"
|
29
|
+
t.pattern = 'spec/integration/**/*_spec.rb'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'yard'
|
35
|
+
require 'yard/rake/yardoc_task'
|
36
|
+
|
37
|
+
YARD::Rake::YardocTask.new do |t|
|
38
|
+
t.files = %w(lib/**/*.rb)
|
39
|
+
t.options = %w(--output-dir doc/)
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
nil
|
17
43
|
end
|
data/lib/msgr/binding.rb
CHANGED
@@ -1,28 +1,33 @@
|
|
1
1
|
module Msgr
|
2
2
|
# A single binding
|
3
3
|
class Binding
|
4
|
-
|
4
|
+
include Logging
|
5
|
+
attr_reader :connection, :route, :subscription, :dispatcher, :queue
|
5
6
|
|
6
7
|
def initialize(connection, route, dispatcher)
|
7
8
|
@connection = connection
|
8
9
|
@route = route
|
9
10
|
@dispatcher = dispatcher
|
10
11
|
|
11
|
-
|
12
|
+
exchange = connection.exchange
|
13
|
+
@queue = connection.queue route.name
|
12
14
|
|
13
|
-
|
15
|
+
route.keys.each do |key|
|
16
|
+
log(:debug) { "Bind #{key} to #{@queue.name}." }
|
14
17
|
|
15
|
-
|
18
|
+
queue.bind exchange, routing_key: key
|
19
|
+
end
|
20
|
+
|
21
|
+
@subscription = queue.subscribe(ack: true) { |*args| call *args }
|
16
22
|
end
|
17
23
|
|
18
24
|
# Called from Bunny Thread Pool. Will create message object from
|
19
25
|
# provided bunny data and dispatch message to connection.
|
20
26
|
#
|
21
27
|
def call(info, metadata, payload)
|
22
|
-
|
23
|
-
dispatcher.dispatch :call, message
|
28
|
+
dispatcher.dispatch Message.new(connection, info, metadata, payload, route)
|
24
29
|
rescue => error
|
25
|
-
|
30
|
+
log(:error) { "Error received within subscribe handler: #{error.inspect}\n#{error.backtrace.join("\n ")}" }
|
26
31
|
end
|
27
32
|
|
28
33
|
# Cancel subscription to not receive any more messages.
|
@@ -30,5 +35,27 @@ module Msgr
|
|
30
35
|
def release
|
31
36
|
subscription.cancel if subscription
|
32
37
|
end
|
38
|
+
|
39
|
+
def release_if_empty
|
40
|
+
if queue_empty?
|
41
|
+
release
|
42
|
+
true
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def queue_empty?
|
49
|
+
@queue.message_count == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete
|
53
|
+
release
|
54
|
+
queue.delete
|
55
|
+
end
|
56
|
+
|
57
|
+
def purge
|
58
|
+
queue.purge
|
59
|
+
end
|
33
60
|
end
|
34
61
|
end
|
data/lib/msgr/client.rb
CHANGED
@@ -6,22 +6,22 @@ module Msgr
|
|
6
6
|
include Celluloid
|
7
7
|
include Logging
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :uri
|
10
10
|
|
11
|
-
def initialize(config)
|
11
|
+
def initialize(config = {})
|
12
12
|
@uri = URI.parse config[:uri] ? config.delete(:uri) : 'amqp://localhost/'
|
13
|
-
|
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]
|
13
|
+
config[:pass] ||= @uri.password
|
19
14
|
|
20
|
-
@config
|
21
|
-
@
|
22
|
-
@
|
15
|
+
@uri.user = config[:user] ||= @uri.user || 'guest'
|
16
|
+
@uri.scheme = (config[:ssl] ||= @uri.scheme.to_s.downcase == 'amqps') ? 'amqps' : 'amqp'
|
17
|
+
@uri.host = config[:host] ||= @uri.host || '127.0.0.1'
|
18
|
+
@uri.port = config[:port] ||= @uri.port
|
19
|
+
@uri.path = config[:vhost] ||= @uri.path.present? ? @uri.path : '/'
|
20
|
+
config.reject! { |_,v| v.nil? }
|
23
21
|
|
24
|
-
@
|
22
|
+
@config = config
|
23
|
+
@bunny = Bunny.new config
|
24
|
+
@pool = Pool.new Dispatcher, size: config[:size]
|
25
25
|
end
|
26
26
|
|
27
27
|
def running?; @running end
|
@@ -31,6 +31,10 @@ module Msgr
|
|
31
31
|
@routes ||= Routes.new
|
32
32
|
end
|
33
33
|
|
34
|
+
def new_connection
|
35
|
+
@connection = Connection.new @bunny, routes, @pool, prefix: @config[:prefix]
|
36
|
+
end
|
37
|
+
|
34
38
|
def reload
|
35
39
|
raise StandardError.new 'Client not running.' unless running?
|
36
40
|
log(:info) { 'Reload client.' }
|
@@ -39,8 +43,7 @@ module Msgr
|
|
39
43
|
@connection.terminate
|
40
44
|
|
41
45
|
log(:debug) { 'Create new connection.' }
|
42
|
-
|
43
|
-
@connection = Connection.new @bunny, routes, pool
|
46
|
+
new_connection
|
44
47
|
|
45
48
|
log(:info) { 'Client reloaded.' }
|
46
49
|
end
|
@@ -50,26 +53,28 @@ module Msgr
|
|
50
53
|
|
51
54
|
@bunny.start
|
52
55
|
@pool.start
|
56
|
+
@running = true
|
57
|
+
new_connection
|
53
58
|
|
54
|
-
|
55
|
-
@connection = Connection.new @bunny, routes, pool
|
56
|
-
|
57
|
-
log(:info) { "Client started. pool: #{pool.size}" }
|
59
|
+
log(:info) { 'Client started.' }
|
58
60
|
end
|
59
61
|
|
60
|
-
def stop
|
62
|
+
def stop(opts = {})
|
61
63
|
return unless running?
|
64
|
+
opts.reverse_merge! timeout: 10, delete: false
|
62
65
|
|
63
|
-
|
64
|
-
log(:info) { 'Graceful shutdown client...' }
|
66
|
+
stop_connection opts
|
65
67
|
|
66
|
-
@
|
67
|
-
|
68
|
+
@running = false
|
69
|
+
wait_for_graceful_shutdown opts if opts[:timeout]
|
68
70
|
|
69
71
|
log(:debug) { 'Terminating...' }
|
70
72
|
|
73
|
+
if opts[:delete]
|
74
|
+
log(:debug) { 'Delete connection.' }
|
75
|
+
@connection.delete
|
76
|
+
end
|
71
77
|
@connection.terminate
|
72
|
-
@pool.terminate
|
73
78
|
@bunny.stop
|
74
79
|
|
75
80
|
log(:info) { 'Terminated.' }
|
@@ -78,5 +83,30 @@ module Msgr
|
|
78
83
|
def publish(routing_key, payload)
|
79
84
|
@connection.publish payload, routing_key: routing_key
|
80
85
|
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def stop_connection(opts)
|
89
|
+
timeout = [opts[:wait_empty].to_i, 1].max
|
90
|
+
|
91
|
+
begin
|
92
|
+
if opts[:wait_empty]
|
93
|
+
log(:info) { "Shutdown requested: Wait until all queues are empty. (TIMEOUT: #{timeout}s)" }
|
94
|
+
@connection.future(:release, true).value timeout
|
95
|
+
else
|
96
|
+
@connection.future(:release).value timeout
|
97
|
+
end
|
98
|
+
rescue TimeoutError
|
99
|
+
log(:warn) { "Could release connection within #{timeout} seconds." }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def wait_for_graceful_shutdown(opts)
|
104
|
+
timeout = [opts[:timeout].to_i, 0].max
|
105
|
+
log(:info) { "Attempting graceful shutdown within #{timeout} seconds..." }
|
106
|
+
|
107
|
+
@pool.future(:stop).value timeout
|
108
|
+
rescue TimeoutError
|
109
|
+
log(:warn) { "Could not shutdown pool within #{timeout} seconds." }
|
110
|
+
end
|
81
111
|
end
|
82
112
|
end
|
data/lib/msgr/connection.rb
CHANGED
@@ -4,32 +4,37 @@ module Msgr
|
|
4
4
|
include Celluloid
|
5
5
|
include Logging
|
6
6
|
|
7
|
-
attr_reader :conn, :
|
7
|
+
attr_reader :conn, :dispatcher, :routes, :opts
|
8
8
|
finalizer :close
|
9
9
|
|
10
|
-
def initialize(conn, routes,
|
11
|
-
@conn
|
12
|
-
@
|
13
|
-
@routes
|
10
|
+
def initialize(conn, routes, dispatcher, opts = {})
|
11
|
+
@conn = conn
|
12
|
+
@dispatcher = dispatcher
|
13
|
+
@routes = routes
|
14
|
+
@opts = opts
|
14
15
|
|
15
16
|
@channel = conn.create_channel
|
16
17
|
@channel.prefetch(10)
|
17
18
|
|
18
|
-
|
19
|
+
bind
|
19
20
|
end
|
20
21
|
|
21
|
-
def rebind
|
22
|
-
routes = self.routes unless routes
|
23
|
-
|
24
|
-
# First release old bindings
|
22
|
+
def rebind
|
25
23
|
release
|
24
|
+
bind
|
25
|
+
end
|
26
26
|
|
27
|
+
def bind
|
27
28
|
# Create new bindings
|
28
|
-
routes.each { |route| bindings << Binding.new(Actor.current, route,
|
29
|
+
routes.each { |route| bindings << Binding.new(Actor.current, route, dispatcher) }
|
29
30
|
|
30
31
|
log(:debug) { 'New routes bound.' }
|
31
32
|
end
|
32
33
|
|
34
|
+
def prefix(name = '')
|
35
|
+
opts[:prefix] ? "#{opts[:prefix]}-#{name}" : name
|
36
|
+
end
|
37
|
+
|
33
38
|
# Used to store al bindings. Allows use to
|
34
39
|
# release bindings when receiver should not longer
|
35
40
|
# receive messages but channel need to be open
|
@@ -40,36 +45,78 @@ module Msgr
|
|
40
45
|
end
|
41
46
|
|
42
47
|
def queue(name)
|
43
|
-
@channel.queue
|
48
|
+
@channel.queue(prefix(name), durable: true).tap do |queue|
|
49
|
+
log(:debug) { "Create queue #{queue.name} (durable: #{queue.durable?}, auto_delete: #{queue.auto_delete?})" }
|
50
|
+
end
|
44
51
|
end
|
45
52
|
|
46
53
|
def exchange
|
47
|
-
@exchange
|
54
|
+
unless @exchange
|
55
|
+
@exchange = @channel.topic prefix('msgr'), durable: true
|
56
|
+
|
57
|
+
log(:debug) { "Created exchange #{@exchange.name} (type: #{@exchange.type}, durable: #{@exchange.durable?}, auto_delete: #{@exchange.auto_delete?})" }
|
58
|
+
end
|
59
|
+
|
60
|
+
@exchange
|
48
61
|
end
|
49
62
|
|
50
63
|
# Release all bindings but do not close channel. Will not
|
51
64
|
# longer receive any message but channel can be used to
|
52
65
|
# acknowledge currently processing messages.
|
53
66
|
#
|
54
|
-
def release
|
67
|
+
def release(wait = false)
|
68
|
+
return unless bindings.any?
|
69
|
+
|
70
|
+
log(:debug) { "Release all bindings#{wait ? ' after queues are empty': ''}..." }
|
71
|
+
|
72
|
+
if wait
|
73
|
+
binds = bindings.dup
|
74
|
+
while binds.any?
|
75
|
+
binds.reject! { |b| b.release_if_empty }
|
76
|
+
sleep 1
|
77
|
+
end
|
78
|
+
else
|
79
|
+
bindings.each &:release
|
80
|
+
end
|
81
|
+
|
82
|
+
log(:debug) { 'All bindings released.' }
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete
|
55
86
|
return unless bindings.any?
|
56
87
|
|
57
|
-
log(:debug) { '
|
88
|
+
log(:debug) { 'Delete all bindings and exchange.' }
|
58
89
|
|
59
|
-
bindings.each { |binding| binding.
|
90
|
+
bindings.each { |binding| binding.delete }
|
60
91
|
bindings.clear
|
92
|
+
|
93
|
+
@exchange.delete if @exchange
|
61
94
|
end
|
62
95
|
|
63
96
|
def publish(payload, opts = {})
|
97
|
+
opts[:routing_key] ||= opts[:to]
|
98
|
+
raise ArgumentError, 'Missing routing key.' unless opts[:routing_key]
|
99
|
+
|
64
100
|
log(:debug) { "Publish message to #{opts[:routing_key]}" }
|
65
101
|
|
66
|
-
|
102
|
+
begin
|
103
|
+
payload = JSON.generate(payload)
|
104
|
+
exchange.publish payload, opts.merge(persistent: true, content_type: 'application/json')
|
105
|
+
rescue => error
|
106
|
+
exchange.publish payload.to_s, opts.merge(persistent: true, content_type: 'application/text')
|
107
|
+
end
|
67
108
|
end
|
68
109
|
|
69
110
|
def ack(delivery_tag)
|
111
|
+
log(:debug) { "Ack message: #{delivery_tag}" }
|
70
112
|
@channel.ack delivery_tag
|
71
113
|
end
|
72
114
|
|
115
|
+
def reject(delivery_tag, requeue = true)
|
116
|
+
log(:debug) { "Reject message: #{delivery_tag}" }
|
117
|
+
@channel.reject delivery_tag, requeue
|
118
|
+
end
|
119
|
+
|
73
120
|
def close
|
74
121
|
@channel.close if @channel.open?
|
75
122
|
log(:debug) { 'Connection closed.' }
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Msgr
|
2
|
+
|
3
|
+
class Consumer
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
attr_reader :message
|
7
|
+
delegate :payload, to: :@message
|
8
|
+
delegate :action, to: :'@message.route'
|
9
|
+
delegate :consumer, to: :'@message.consumer'
|
10
|
+
|
11
|
+
def dispatch(message)
|
12
|
+
@message = message
|
13
|
+
|
14
|
+
action = message.route.action.to_sym
|
15
|
+
raise Msgr::NoAction.new "No action `#{action}` for `#{self.class.name}`." unless respond_to? action
|
16
|
+
|
17
|
+
log(:debug) { "Invoke action #{action.inspect}." }
|
18
|
+
|
19
|
+
send action
|
20
|
+
|
21
|
+
log(:debug) { "Action #{action.inspect} done." }
|
22
|
+
end
|
23
|
+
|
24
|
+
def publish(data, opts = {})
|
25
|
+
Msgr.client.publish(data, opts)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/msgr/dispatcher.rb
CHANGED
@@ -7,16 +7,23 @@ module Msgr
|
|
7
7
|
class Dispatcher
|
8
8
|
include Logging
|
9
9
|
|
10
|
-
def
|
10
|
+
def call(message)
|
11
|
+
dispatch message
|
12
|
+
|
13
|
+
# Acknowledge message unless it is already acknowledged.
|
14
|
+
message.ack unless message.acked?
|
11
15
|
|
16
|
+
log(:warn) { "Message not acked!" } unless message.acked?
|
12
17
|
end
|
13
18
|
|
14
|
-
def
|
15
|
-
|
19
|
+
def dispatch(message)
|
20
|
+
consumer_class = Object.const_get message.route.consumer
|
16
21
|
|
17
|
-
message.
|
22
|
+
log(:debug) { "Dispatch message to #{consumer_class.name}" }
|
18
23
|
|
19
|
-
|
24
|
+
consumer_class.new.dispatch message
|
25
|
+
rescue => error
|
26
|
+
log(:error) { error }
|
20
27
|
end
|
21
28
|
|
22
29
|
def to_s
|
data/lib/msgr/logging.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Msgr
|
2
|
+
class Message
|
3
|
+
|
4
|
+
#
|
5
|
+
#
|
6
|
+
module Acknowledge
|
7
|
+
|
8
|
+
# Check if message is already acknowledged.
|
9
|
+
#
|
10
|
+
# @return [Boolean] True if message is acknowledged, false otherwise.
|
11
|
+
# @api public
|
12
|
+
#
|
13
|
+
def acked?
|
14
|
+
@acked ? true : false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Send message acknowledge to broker unless message is
|
18
|
+
# already acknowledged.
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
#
|
22
|
+
def ack
|
23
|
+
unless acked?
|
24
|
+
@acked = true
|
25
|
+
@connection.future(:ack, delivery_info.delivery_tag).value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/msgr/message.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module Msgr
|
2
2
|
|
3
|
+
require 'msgr/message/acknowledge'
|
4
|
+
|
3
5
|
class Message
|
4
|
-
|
6
|
+
include Acknowledge
|
7
|
+
attr_reader :delivery_info, :metadata, :payload, :route
|
5
8
|
|
6
9
|
def initialize(connection, delivery_info, metadata, payload, route)
|
7
10
|
@connection = connection
|
@@ -9,10 +12,14 @@ module Msgr
|
|
9
12
|
@metadata = metadata
|
10
13
|
@payload = payload
|
11
14
|
@route = route
|
15
|
+
|
16
|
+
if content_type == 'application/json'
|
17
|
+
@payload = JSON[payload].symbolize_keys
|
18
|
+
end
|
12
19
|
end
|
13
20
|
|
14
|
-
def
|
15
|
-
@
|
21
|
+
def content_type
|
22
|
+
@metadata.content_type
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|