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,19 @@
1
+ module Mimi
2
+ module Messaging
3
+ class ConnectionError < StandardError
4
+ end # class ConnectionError
5
+
6
+ class RequestError < StandardError
7
+ attr_accessor :params
8
+
9
+ def initialize(message = 'failed to process request', params = {})
10
+ @message = message
11
+ @params = params.dup
12
+ end
13
+
14
+ def to_s
15
+ @message
16
+ end
17
+ end # class RequestError
18
+ end # module Messaging
19
+ end # module Mimi
@@ -0,0 +1,72 @@
1
+ module Mimi
2
+ module Messaging
3
+ class Listener < RequestProcessor
4
+ DEFAULT_DURABLE_QUEUE_OPTIONS = {
5
+ exclusive: false,
6
+ durable: true,
7
+ auto_delete: false
8
+ }
9
+
10
+ DEFAULT_TEMPORARY_QUEUE_OPTIONS = {
11
+ exclusive: true,
12
+ durable: false,
13
+ auto_delete: true
14
+ }
15
+
16
+ abstract!
17
+
18
+ queue_options DEFAULT_TEMPORARY_QUEUE_OPTIONS
19
+
20
+ def self.queue(name = nil, opts = {})
21
+ return super unless name
22
+ queue_name(name)
23
+ if name && name != ''
24
+ queue_options(DEFAULT_DURABLE_QUEUE_OPTIONS.merge(opts))
25
+ else
26
+ queue_options(DEFAULT_TEMPORARY_QUEUE_OPTIONS.merge(opts))
27
+ end
28
+ end
29
+
30
+ def self.default_queue_name
31
+ ''
32
+ end
33
+
34
+ # Sets or gets notification resource name
35
+ #
36
+ def self.notification(name = nil, _opts = {})
37
+ notification_name name
38
+ true
39
+ end
40
+
41
+ # Sets or gets queue name
42
+ #
43
+ def self.notification_name(name = nil)
44
+ if name && @notification_name
45
+ raise "#{self} has already registered '#{@notification_name}' as notification name"
46
+ end
47
+ (@notification_name ||= name) || default_notification_name
48
+ end
49
+
50
+ # Default (inferred) notification name
51
+ #
52
+ def self.default_notification_name
53
+ class_name_to_resource_name(name, 'listener')
54
+ end
55
+
56
+ def self.resource_name
57
+ notification_name
58
+ end
59
+
60
+ def self.request_type(_d, _m, _p)
61
+ :broadcast
62
+ end
63
+
64
+ def self.construct_queue
65
+ exchange = channel.fanout(resource_name)
66
+ q = channel.create_queue(queue_name, queue_options)
67
+ q.bind(exchange)
68
+ q
69
+ end
70
+ end # class Listener
71
+ end # module Messaging
72
+ end # module Mimi
@@ -0,0 +1,74 @@
1
+ require 'json'
2
+ require 'msgpack'
3
+
4
+ module Mimi
5
+ module Messaging
6
+ class Message < Hashie::Mash
7
+ def self.queue(name, _opts = {})
8
+ @queue_name = name
9
+ end
10
+
11
+ def self.queue_name
12
+ @queue_name || default_queue_name
13
+ end
14
+
15
+ def self.default_queue_name
16
+ Mimi::Messaging::RequestProcessor.class_name_to_resource_name(self, 'message')
17
+ end
18
+
19
+ def self.get(name, data = {}, opts = {})
20
+ headers = {
21
+ method_name: name.to_s,
22
+ Mimi::Messaging::CONTEXT_ID_KEY => Mimi::Messaging.logger.context_id
23
+ }
24
+ _d, _m, response = Mimi::Messaging.get(
25
+ queue_name, encode(data), opts.deep_merge(headers: headers)
26
+ )
27
+ raise Timeout::Error unless response
28
+ message = new(decode(response))
29
+ raise RequestError.new(message.error, Message.new(message.params)) if message.error?
30
+ message
31
+ end
32
+
33
+ def self.post(name, data = {}, opts = {})
34
+ headers = {
35
+ method_name: name.to_s,
36
+ Mimi::Messaging::CONTEXT_ID_KEY => Mimi::Messaging.logger.context_id
37
+ }
38
+ Mimi::Messaging.post(
39
+ queue_name, encode(data), opts.deep_merge(headers: headers)
40
+ )
41
+ end
42
+
43
+ def self.add_method(name, &block)
44
+ self.class.instance_eval do
45
+ define_method(name, &block)
46
+ end
47
+ end
48
+
49
+ def self.methods(*names)
50
+ names.each do |method_name|
51
+ add_method(method_name) do |*params|
52
+ get(method_name, *params)
53
+ end
54
+ add_method("#{method_name}!") do |*params|
55
+ post(method_name, *params)
56
+ nil
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.encode(data)
62
+ MessagePack.pack(data) # data.to_json
63
+ end
64
+
65
+ def self.decode(raw_message)
66
+ MessagePack.unpack(raw_message) # JSON.parse(raw_message)
67
+ end
68
+
69
+ def to_s
70
+ to_hash.to_s
71
+ end
72
+ end # class Message
73
+ end # module Messaging
74
+ end # module Mimi
@@ -0,0 +1,13 @@
1
+ #
2
+ # Replaces real Connection and Channel with MockConnection and MockChannel
3
+ # that stubs connections to RabbitMQ.
4
+ #
5
+ # Replaces RequestProcessor with MockRequestProcessor
6
+ #
7
+
8
+ require_relative 'mock/connection'
9
+ require_relative 'mock/request_processor'
10
+ require_relative 'mock/request'
11
+
12
+ Mimi::Messaging.send :remove_const, :Connection
13
+ Mimi::Messaging::Connection = Mimi::Messaging::MockConnection
@@ -0,0 +1,153 @@
1
+ require 'bunny'
2
+
3
+ module Mimi
4
+ module Messaging
5
+ class MockConnection
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 = { mock_connection: bunny_params }
26
+ end
27
+
28
+ # Starts the connection, opening actual connection to RabbitMQ
29
+ #
30
+ def start
31
+ @started = true
32
+ end
33
+
34
+ # Stops the connection
35
+ #
36
+ def stop
37
+ @channel_pool = {}
38
+ @started = false
39
+ end
40
+
41
+ def started?
42
+ @started
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
+ MockChannel.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 MockQueue
72
+ def initialize(*)
73
+ end
74
+ end
75
+
76
+ class MockChannel
77
+ attr_reader :options, :connection
78
+
79
+ DEFAULT_OPTIONS = {
80
+ concurrency: 1
81
+ }
82
+ DEFAULT_GET_TIMEOUT = 60 # seconds
83
+
84
+ def initialize(connection, opts = {})
85
+ @connection = connection
86
+ @options = DEFAULT_OPTIONS.merge(opts)
87
+ @channel = { mock_channel: @connection, opts: options[:concurrency] }
88
+ @mutex = Mutex.new
89
+ end
90
+
91
+ def create_queue(name, opts = {})
92
+ MockQueue.new(name, opts)
93
+ end
94
+
95
+ def reply_queue
96
+ @reply_queue ||= create_queue('', exclusive: true)
97
+ end
98
+
99
+ def ack(tag)
100
+ raise "Not implemented"
101
+ end
102
+
103
+ def fanout(name)
104
+ raise "Not implemented"
105
+ end
106
+
107
+ def active?
108
+ true
109
+ end
110
+
111
+ # Sends a raw RabbitMQ message to a given direct exchange
112
+ #
113
+ # @param queue_name [String] Queue name to send the message to
114
+ # @param raw_message [String]
115
+ # @param params [Hash] Message params (metadata)
116
+ #
117
+ def post(queue_name, raw_message, params = {})
118
+ true
119
+ end
120
+
121
+ # Sends a raw RabbitMQ message to a given direct exchange and listens for response
122
+ #
123
+ # @param queue_name [String] Queue name to send the message to
124
+ # @param raw_message [String]
125
+ # @param params [Hash] Message params (metadata)
126
+ #
127
+ # @param params[:timeout] [Integer] (optional) Timeout in seconds
128
+ #
129
+ # @return [nil,Array]
130
+ #
131
+ def get(queue_name, raw_message, params = {})
132
+ nil
133
+ end
134
+
135
+ # Sends a raw RabbitMQ message to a given fanout exchange
136
+ #
137
+ # @param fanout_name [String] Fanout exchange name to send the message to
138
+ # @param raw_message [String]
139
+ # @param params [Hash] Message params (metadata)
140
+ #
141
+ def broadcast(fanout_name, raw_message, params = {})
142
+ true
143
+ end
144
+
145
+ private
146
+
147
+ def publish(exchange, raw_message, params = {})
148
+ true
149
+ end
150
+ end # class Channel
151
+ end # class Connection
152
+ end # module Messaging
153
+ end # module Mimi
@@ -0,0 +1,18 @@
1
+ module Mimi
2
+ module Messaging
3
+ class Request
4
+ attr_reader :response
5
+
6
+ def send_response(data = {})
7
+ return if !get? || replied?
8
+ raise ArgumentError, 'Invalid response format, Hash is expected' unless data.is_a?(Hash)
9
+ raw_message = Mimi::Messaging::Message.encode(data)
10
+ @response = Mimi::Messaging::Message.decode(raw_message)
11
+ request_processor.connection.post(
12
+ reply_to_queue_name, raw_message, correlation_id: metadata[:correlation_id]
13
+ )
14
+ @replied = true
15
+ end
16
+ end # class Request
17
+ end # module Messaging
18
+ end # module Mimi
@@ -0,0 +1,92 @@
1
+ module Mimi
2
+ module Messaging
3
+ class RequestProcessor
4
+ attr_reader :result, :request
5
+
6
+ def self.started?
7
+ !@consumer.nil?
8
+ end
9
+
10
+ # Mock start
11
+ #
12
+ def self.start
13
+ return if abstract?
14
+ raise "#{name} already started" if started?
15
+ logger.debug "#{self} starting to serve '#{resource_name}' (#{exposed_methods})"
16
+ @queue = construct_queue
17
+ @consumer_mutex = Mutex.new
18
+ @consumer_mutex.synchronize do
19
+ @consumer = true
20
+ end
21
+ # consumer created, mutex released
22
+ end
23
+
24
+ # Mock stop
25
+ #
26
+ def self.stop
27
+ return if abstract?
28
+ raise "#{name} already stopped" unless started?
29
+ @consumer_mutex.synchronize do
30
+ @consumer = nil
31
+ end
32
+ @consumer = nil
33
+ @queue = nil
34
+ @channel = nil
35
+ @connection = nil
36
+ end
37
+
38
+ # MockRequestProcessor helper GET methods
39
+ #
40
+ # @param method_name [Symbol]
41
+ # @param message [Hash]
42
+ #
43
+ # @return [Mimi::Messaging::Message]
44
+ #
45
+ def self.get(method_name, message)
46
+ metadata = Mimi::Messaging::Message.new(
47
+ correlation_id: 1,
48
+ reply_to: 'mock_client',
49
+ headers: {
50
+ 'method_name' => method_name.to_s,
51
+ 'c' => 'mock-context'
52
+ }
53
+ )
54
+ d = Mimi::Messaging::Message.new()
55
+ raw_message = Mimi::Messaging::Message.new(message).to_msgpack
56
+ request_processor = new(d, metadata, raw_message)
57
+ request_processor.request.response
58
+ end
59
+
60
+ # MockRequestProcessor helper POST method
61
+ #
62
+ # @param method_name [Symbol]
63
+ # @param message [Hash]
64
+ #
65
+ # @return [Mimi::Messaging::Message]
66
+ #
67
+ def self.post(method_name, message)
68
+ metadata = Mimi::Messaging::Message.new(
69
+ headers: {
70
+ 'method_name' => method_name.to_s,
71
+ 'c' => 'context-id'
72
+ }
73
+ )
74
+ d = Mimi::Messaging::Message.new()
75
+ raw_message = Mimi::Messaging::Message.new(message)
76
+ request_processor = new(d, metadata, raw_message)
77
+ nil
78
+ end
79
+
80
+ # MockRequestProcessor helper BROADCAST method
81
+ #
82
+ # @param method_name [Symbol]
83
+ # @param message [Hash]
84
+ #
85
+ # @return [Mimi::Messaging::Message]
86
+ #
87
+ def self.broadcast(method_name, message)
88
+ post(method_name, message)
89
+ end
90
+ end # class RequestProcessor
91
+ end # module Messaging
92
+ end # module Mimi