msgr 0.0.0 → 0.1.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 +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
|
[](http://badge.fury.io/rb/msgr)
|
4
4
|
[](https://travis-ci.org/jgraichen/msgr)
|
5
5
|
[](https://coveralls.io/r/jgraichen/msgr)
|
6
6
|
[](https://codeclimate.com/github/jgraichen/msgr)
|
7
7
|
[](https://gemnasium.com/jgraichen/msgr)
|
8
|
-
[](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
|