mimi-messaging 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4caf4a7e6908dcb328deefefc09457ac83e45d01
4
+ data.tar.gz: 1549a96a6f3deeff86e8df609fe87292b4d67fb0
5
+ SHA512:
6
+ metadata.gz: d4871e67c8cbe628698e0b3fd337be92c0b86d97ca08b5ace97e56ab3fb269c7e07b1ad9f307bc7df0618494e89e05ece78445a5cca5d4a5d96baf0768a7ab8d
7
+ data.tar.gz: fbd677968172b23bd2f735def0dbace80b9fffa5ddb65a9994fdc72137474db5c6e1b723c9cbe6d89b1c306a52119b76e9a8d09d1322a059f14999ad14cd7e75
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at alex@kukushk.in. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Alex Kukushkin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # mimi-messaging
2
+
3
+ Communications via RabbitMQ for [mimi](https://github.com/kukushkin/mimi).
4
+
5
+ [in development]
6
+
7
+
8
+ ## License
9
+
10
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
11
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'mimi/messaging'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,124 @@
1
+ require 'mimi/core'
2
+ require 'mimi/logger'
3
+
4
+ module Mimi
5
+ module Messaging
6
+ include Mimi::Core::Module
7
+ include Mimi::Logger::Instance
8
+
9
+ # key in the message headers that is used to pass context id
10
+ CONTEXT_ID_KEY = 'c'.freeze
11
+
12
+ default_options(
13
+ require_files: 'app/messaging/**/*.rb',
14
+ mq_host: 'localhost',
15
+ mq_port: 5672,
16
+ mq_username: nil,
17
+ mq_password: nil,
18
+ mq_vhost: nil
19
+ )
20
+
21
+ def self.module_path
22
+ Pathname.new(__dir__).join('..').join('..').expand_path
23
+ end
24
+
25
+ def self.module_manifest
26
+ {
27
+ mq_host: {
28
+ desc: 'RabbitMQ host',
29
+ default: 'localhost'
30
+ },
31
+ mq_port: {
32
+ desc: 'RabbitMQ port',
33
+ default: 5672
34
+ },
35
+ mq_username: {
36
+ desc: 'RabbitMQ username',
37
+ default: nil
38
+ },
39
+ mq_password: {
40
+ desc: 'RabbitMQ password',
41
+ default: nil
42
+ }
43
+ }
44
+ end
45
+
46
+ def self.configure(*)
47
+ super
48
+ connections << Mimi::Messaging::Connection.new(module_options)
49
+ end
50
+
51
+ # @return [Array<Mimi::Messaging::Connection>]
52
+ #
53
+ def self.connections
54
+ @connections ||= []
55
+ end
56
+
57
+ # @return [Array<Class < Mimi::Messaging::RequestProcessor>]
58
+ #
59
+ def self.request_processor_classes
60
+ @request_processor_classes ||= []
61
+ end
62
+
63
+ # @param [Class < Mimi::Messaging::RequestProcessor]
64
+ #
65
+ def self.register_request_processor_class(request_processor_class)
66
+ request_processor_classes << request_processor_class
67
+ end
68
+
69
+ # Selects the connection to be used for sending/receiving messages from/to given queue
70
+ #
71
+ # @param queue_name [String]
72
+ # @return [Mimi::Messaging::Connection]
73
+ #
74
+ def self.connection_for(queue_name)
75
+ connection_for_queue = connections.select do |c|
76
+ c.queue_prefix && (
77
+ (c.queue_prefix.is_a?(String) && queue_name.start_with?(c.queue_prefix)) ||
78
+ (c.queue_prefix.is_a?(Array) && c.queue_prefix.any? { |qp| queue_name.start_with?(qp) })
79
+ )
80
+ end.first
81
+ return connection_for_queue if connection_for_queue
82
+ connections.select { |c| c.queue_prefix.nil? }.first
83
+ end
84
+
85
+ def self.post(queue_name, raw_message, params = {})
86
+ connection_for(queue_name).post(queue_name, raw_message, params)
87
+ end
88
+
89
+ def self.get(queue_name, raw_message, params = {})
90
+ connection_for(queue_name).get(queue_name, raw_message, params)
91
+ end
92
+
93
+ def self.broadcast(queue_name, raw_message, params = {})
94
+ connection_for(queue_name).broadcast(queue_name, raw_message, params)
95
+ end
96
+
97
+ def self.start
98
+ Mimi.require_files(module_options[:require_files]) if module_options[:require_files]
99
+ connections.each(&:start)
100
+ request_processor_classes.each(&:start)
101
+ super
102
+ end
103
+
104
+ def self.stop
105
+ request_processor_classes.each(&:stop)
106
+ connections.each(&:stop)
107
+ super
108
+ end
109
+ end # module Messaging
110
+ end # module Mimi
111
+
112
+ require_relative 'messaging/version'
113
+ require_relative 'messaging/errors'
114
+ require_relative 'messaging/connection'
115
+ require_relative 'messaging/message'
116
+ require_relative 'messaging/request'
117
+ require_relative 'messaging/request_processor'
118
+ require_relative 'messaging/provider'
119
+ require_relative 'messaging/model'
120
+ require_relative 'messaging/model_provider'
121
+ require_relative 'messaging/notification'
122
+ require_relative 'messaging/listener'
123
+ require_relative 'messaging/msgpack/type_packer'
124
+ require_relative 'messaging/msgpack/msgpack_ext'
@@ -0,0 +1,181 @@
1
+ require 'bunny'
2
+
3
+ module Mimi
4
+ module Messaging
5
+ class Connection
6
+ attr_reader :queue_prefix
7
+
8
+ # Creates a Connection with given connection params
9
+ #
10
+ # @param params [Hash] Connection params as accepted by Bunny
11
+ # @param params[:queue_prefix] [String] (optional) Use this connection for all communication
12
+ # related to queues, having names starting with given
13
+ # prefix
14
+ #
15
+ def initialize(params = {})
16
+ @queue_prefix = params[:queue_prefix]
17
+ @channel_pool = {}
18
+ bunny_params = {
19
+ host: params[:mq_host],
20
+ port: params[:mq_port],
21
+ username: params[:mq_username],
22
+ password: params[:mq_password],
23
+ vhost: params[:mq_vhost]
24
+ }
25
+ @connection = Bunny.new(bunny_params)
26
+ end
27
+
28
+ # Starts the connection, opening actual connection to RabbitMQ
29
+ #
30
+ def start
31
+ @connection.start
32
+ end
33
+
34
+ # Stops the connection
35
+ #
36
+ def stop
37
+ @connection.close
38
+ @channel_pool = {}
39
+ end
40
+
41
+ def started?
42
+ @connection.status == :open
43
+ end
44
+
45
+ def channel
46
+ raise ConnectionError unless started?
47
+ @channel_pool[Thread.current.object_id] ||= create_channel
48
+ end
49
+
50
+ def create_channel(opts = {})
51
+ Channel.new(@connection, opts)
52
+ end
53
+
54
+ def reply_queue
55
+ raise ConnectionError unless started?
56
+ channel.reply_queue
57
+ end
58
+
59
+ def post(queue_name, raw_message, params = {})
60
+ channel.post(queue_name, raw_message, params)
61
+ end
62
+
63
+ def get(queue_name, raw_message, params = {})
64
+ channel.get(queue_name, raw_message, params)
65
+ end
66
+
67
+ def broadcast(queue_name, raw_message, params = {})
68
+ channel.broadcast(queue_name, raw_message, params)
69
+ end
70
+
71
+ class Channel
72
+ attr_reader :options, :connection
73
+
74
+ DEFAULT_OPTIONS = {
75
+ concurrency: 1
76
+ }
77
+ DEFAULT_GET_TIMEOUT = 60 # seconds
78
+
79
+ def initialize(connection, opts = {})
80
+ @connection = connection
81
+ @options = DEFAULT_OPTIONS.merge(opts)
82
+ @channel = @connection.create_channel(nil, options[:concurrency])
83
+ @mutex = Mutex.new
84
+ end
85
+
86
+ def create_queue(name, opts = {})
87
+ @channel.queue(name, opts)
88
+ end
89
+
90
+ def reply_queue
91
+ @reply_queue ||= create_queue('', exclusive: true)
92
+ end
93
+
94
+ def ack(tag)
95
+ @channel.ack(tag)
96
+ end
97
+
98
+ def fanout(name)
99
+ @channel.fanout(name)
100
+ end
101
+
102
+ def active?
103
+ @channel && @channel.active
104
+ end
105
+
106
+ # Sends a raw RabbitMQ message to a given direct exchange
107
+ #
108
+ # @param queue_name [String] Queue name to send the message to
109
+ # @param raw_message [String]
110
+ # @param params [Hash] Message params (metadata)
111
+ #
112
+ def post(queue_name, raw_message, params = {})
113
+ x = @channel.default_exchange
114
+ params = { routing_key: queue_name }.merge(params.dup)
115
+ publish(x, raw_message, params)
116
+ end
117
+
118
+ # Sends a raw RabbitMQ message to a given direct exchange and listens for response
119
+ #
120
+ # @param queue_name [String] Queue name to send the message to
121
+ # @param raw_message [String]
122
+ # @param params [Hash] Message params (metadata)
123
+ #
124
+ # @param params[:timeout] [Integer] (optional) Timeout in seconds
125
+ #
126
+ # @return [nil,Array]
127
+ #
128
+ def get(queue_name, raw_message, params = {})
129
+ correlation_id = Time.now.utc.to_f.to_s
130
+ params = params.dup.merge(
131
+ reply_to: reply_queue.name,
132
+ correlation_id: correlation_id
133
+ )
134
+ post(queue_name, raw_message, params)
135
+ response = nil
136
+ begin
137
+ Timeout.timeout(params[:timeout] || DEFAULT_GET_TIMEOUT) do
138
+ loop do
139
+ d, m, p = reply_queue.pop
140
+ next if d && m.correlation_id != correlation_id
141
+ response = [d, m, p] if d
142
+ break if response
143
+ sleep 0.001 # s
144
+ end
145
+ end
146
+ rescue Timeout::Error
147
+ # respond with nil
148
+ end
149
+ response
150
+ end
151
+
152
+ # Sends a raw RabbitMQ message to a given fanout exchange
153
+ #
154
+ # @param fanout_name [String] Fanout exchange name to send the message to
155
+ # @param raw_message [String]
156
+ # @param params [Hash] Message params (metadata)
157
+ #
158
+ def broadcast(fanout_name, raw_message, params = {})
159
+ x = @channel.fanout(fanout_name)
160
+ publish(x, raw_message, params)
161
+ end
162
+
163
+ private
164
+
165
+ def publish(exchange, raw_message, params = {})
166
+ # HACK: Connection-level mutex reduces throughoutput, hopefully improves stability (ku)
167
+ @mutex.synchronize do
168
+ # TODO: may be make publishing an atomic operation using a separate thread? (ku)
169
+ exchange.publish(raw_message, params)
170
+ end
171
+ rescue StandardError => e
172
+ # Raising fatal error:
173
+ unless Thread.main == Thread.current
174
+ Thread.main.raise ConnectionError, "failed to publish message in a child thread: #{e}"
175
+ end
176
+ raise ConnectionError, "failed to publish message: #{e}"
177
+ end
178
+ end # class Channel
179
+ end # class Connection
180
+ end # module Messaging
181
+ end # module Mimi