combi 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,227 @@
1
+ require 'combi/buses/bus'
2
+ require 'combi/response_store'
3
+
4
+ module Combi
5
+ class WebSocket < Bus
6
+
7
+ class Server
8
+
9
+ def initialize(bus)
10
+ @bus = bus
11
+ @bus.ready.succeed
12
+ end
13
+
14
+ def start!
15
+ end
16
+
17
+ def stop!
18
+ end
19
+
20
+ def on_open(ws, handler)
21
+ @bus.log "ON OPEN #{handler.inspect}"
22
+ handler.new_session(ws)
23
+ end
24
+
25
+ def on_message(ws, session, raw_message)
26
+ @bus.log "ON MESSAGE #{raw_message}"
27
+ message = JSON.parse(raw_message)
28
+ @bus.on_message(ws, message, session)
29
+ end
30
+
31
+ def on_close(session)
32
+ session && session.close
33
+ end
34
+
35
+ def ws
36
+ @ws
37
+ end
38
+
39
+ end
40
+
41
+ class Client
42
+ require 'faye/websocket'
43
+
44
+ def initialize(remote_api, handler, bus)
45
+ @handler = handler
46
+ @remote_api = remote_api
47
+ @bus = bus
48
+ end
49
+
50
+ def start!
51
+ open_websocket
52
+ end
53
+
54
+ def stop!
55
+ @ws && @ws.close
56
+ @bus.log "stop requested"
57
+ end
58
+
59
+ def restart!
60
+ stop!
61
+ start!
62
+ end
63
+
64
+ def open_websocket
65
+ @bus.log @remote_api
66
+ @ws = ws = Faye::WebSocket::Client.new(@remote_api)
67
+ ws.on :open do |event|
68
+ @bus.log "OPEN"
69
+ @bus.log "HANDLER #{@handler.inspect}"
70
+ @handler.on_open
71
+ @bus.ready.succeed
72
+ end
73
+
74
+ ws.on :message do |event|
75
+ @bus.log "ON MESSAGE: #{event.data}"
76
+ message = JSON.parse(event.data)
77
+ @bus.on_message(ws, message)
78
+ end
79
+
80
+ ws.on :close do |event|
81
+ @bus.log "close #{event.code}: #{event.reason}"
82
+ @ws = ws = nil
83
+ end
84
+
85
+ ws.on :error do |event|
86
+ @bus.log "received error: #{event.inspect}"
87
+ stop!
88
+ end
89
+ end
90
+
91
+ def ws
92
+ @bus.log "ws present: #{@ws != nil}"
93
+ @ws
94
+ end
95
+
96
+ end
97
+
98
+ attr_reader :handlers, :ready
99
+
100
+ def initialize(options)
101
+ super
102
+ @handlers = {}
103
+ end
104
+
105
+ def post_initialize
106
+ @ready = EventMachine::DefaultDeferrable.new
107
+ @response_store = Combi::ResponseStore.new
108
+ if @options[:remote_api]
109
+ require 'eventmachine'
110
+ @machine = Client.new(@options[:remote_api], @options[:handler], self)
111
+ else
112
+ @machine = Server.new(self)
113
+ end
114
+ end
115
+
116
+ def start!
117
+ @machine.start!
118
+ end
119
+
120
+ def stop!
121
+ @machine.stop!
122
+ end
123
+
124
+ def manage_request(env, handler)
125
+ require 'faye/websocket'
126
+
127
+ return unless Faye::WebSocket.websocket?(env)
128
+ @ws = ws = Faye::WebSocket.new(env)
129
+ session = nil
130
+
131
+ ws.on :message do |event|
132
+ @machine.on_message(ws, session, event.data)
133
+ end
134
+
135
+ ws.on :open do |event|
136
+ session = @machine.on_open(ws, handler)
137
+ end
138
+
139
+ ws.on :close do |event|
140
+ @machine.on_close(session)
141
+ end
142
+ # Return async Rack response
143
+ ws.rack_response
144
+ end
145
+
146
+ def manage_ws_event(ws, handler)
147
+ session = nil
148
+
149
+ ws.onmessage do |raw_message|
150
+ @machine.on_message(ws, session, raw_message)
151
+ end
152
+
153
+ ws.onopen do |handshake|
154
+ session = @machine.on_open(ws, handler)
155
+ end
156
+
157
+ ws.onclose do
158
+ @machine.on_close(session)
159
+ end
160
+ end
161
+
162
+ def on_message(ws, message, session = nil)
163
+ if message['correlation_id'] && message.has_key?('result')
164
+ @response_store.handle_rpc_response(message)
165
+ log "Stored message with correlation_id #{message['correlation_id']} - #{message.inspect}"
166
+ return
167
+ end
168
+ service_name = message['service']
169
+ kind = message['kind']
170
+ payload = message['payload'] || {}
171
+ payload['session'] = session
172
+ response = invoke_service(service_name, kind, payload)
173
+
174
+ msg = {result: 'ok', correlation_id: message['correlation_id']}
175
+
176
+ if response.respond_to? :succeed
177
+ response.callback do |service_response|
178
+ msg[:response] = service_response
179
+ ws.send(msg.to_json)
180
+ end
181
+ else
182
+ msg[:response] = response
183
+ ws.send(msg.to_json)
184
+ end
185
+ end
186
+
187
+ def invoke_service(service_name, kind, payload)
188
+ handler = handlers[service_name.to_s]
189
+ if handler
190
+ service_instance = handler[:service_instance]
191
+ if service_instance.respond_to? kind
192
+ response = service_instance.send(kind, payload)
193
+ else
194
+ log "[WARNING] Service #{service_name}(#{service_instance.class.name}) does not respond to message #{kind}"
195
+ end
196
+ else
197
+ log "[WARNING] Service #{service_name} not found"
198
+ log "[WARNING] handlers: #{handlers.keys.inspect}"
199
+ end
200
+ end
201
+
202
+ def respond_to(service_instance, handler, options = {})
203
+ log "registering #{handler}"
204
+ handlers[handler.to_s] = {service_instance: service_instance, options: options}
205
+ log "handlers: #{handlers.keys.inspect}"
206
+ end
207
+
208
+ def request(name, kind, message, options = {})
209
+ options[:timeout] ||= RPC_DEFAULT_TIMEOUT
210
+ msg = {
211
+ service: name,
212
+ kind: kind,
213
+ payload: message
214
+ }
215
+ correlation_id = rand(10_000_000).to_s
216
+ msg[:correlation_id] = correlation_id
217
+ waiter = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
218
+ @ready.callback do |r|
219
+ web_socket = @machine.ws || options[:ws]
220
+ log "sending request #{msg.inspect}"
221
+ web_socket.send msg.to_json
222
+ end
223
+ waiter
224
+ end
225
+
226
+ end
227
+ end
@@ -0,0 +1,18 @@
1
+ require 'timeout'
2
+
3
+ module Combi
4
+
5
+ def self.wait_for(defer, options = {}, &block)
6
+ options[:timeout] ||= 2
7
+ poll_time = options[:timeout] / 10
8
+ resolved = false
9
+ defer.callback { |response|
10
+ resolved = true
11
+ block.call response
12
+ }
13
+ Timeout::timeout(options[:timeout]) do
14
+ sleep poll_time while !resolved
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,102 @@
1
+ require 'amqp'
2
+ require 'amqp/utilities/event_loop_helper'
3
+
4
+ module Combi
5
+ class QueueService
6
+
7
+ RPC_DEFAULT_TIMEOUT = 1
8
+ RPC_WAIT_PERIOD = 0.01
9
+
10
+ attr_accessor :rpc_callback
11
+
12
+ def initialize(config, options)
13
+ @config = config
14
+ @options = options
15
+ @rpc_queue = nil
16
+ @ready_defer = EventMachine::DefaultDeferrable.new
17
+ end
18
+
19
+ def ready(&block)
20
+ @ready_defer.callback &block
21
+ end
22
+
23
+ def log(message)
24
+ return unless @debug_mode ||= ENV['DEBUG'] == 'true'
25
+ puts "#{object_id} #{self.class.name} #{message}"
26
+ end
27
+
28
+ def start
29
+ connect @config do
30
+ if @options[:rpc] == :enabled
31
+ create_rpc_queue
32
+ else
33
+ puts "ready"
34
+ @ready_defer.succeed
35
+ end
36
+ end
37
+ end
38
+
39
+ def connect(config, &after_connect)
40
+ @amqp_conn = AMQP.connect(config) do |connection, open_ok|
41
+ @channel = AMQP::Channel.new @amqp_conn
42
+ @channel.auto_recovery = true
43
+ @exchange = @channel.direct ''
44
+ after_connect.call
45
+ end
46
+ end
47
+
48
+ def disconnect
49
+ @amqp_conn.close do
50
+ puts "disconnected from RABBIT"
51
+ end
52
+ end
53
+
54
+ def publish(*args, &block)
55
+ args[0] = args[0].to_json unless args[0].is_a? String
56
+ @exchange.publish *args, &block
57
+ end
58
+
59
+ def queue(name, options = {}, &block)
60
+ @channel.queue(name, options, &block)
61
+ end
62
+
63
+ def acknowledge(delivery_info)
64
+ @channel.acknowledge(delivery_info.delivery_tag, false)
65
+ end
66
+
67
+ def respond(response, delivery_info)
68
+ response = response.call if response.respond_to? :call
69
+ publish response, routing_key: delivery_info.reply_to, correlation_id: delivery_info.correlation_id
70
+ end
71
+
72
+ def create_rpc_queue
73
+ @rpc_queue.unsubscribe unless @rpc_queue.nil?
74
+ @rpc_queue = queue('', exclusive: true, auto_delete: true) do |rpc_queue|
75
+ log "\tRPC QUEUE: #{@rpc_queue.name}"
76
+ rpc_queue.subscribe do |metadata, response|
77
+ message = {
78
+ 'correlation_id' => metadata.correlation_id,
79
+ 'response' => response
80
+ }
81
+ rpc_callback.call(message) unless rpc_callback.nil?
82
+ end
83
+ @ready_defer.succeed
84
+ end
85
+ end
86
+
87
+ def call(kind, message, options = {})
88
+ log "sending request #{kind} #{message.inspect} with options #{options.inspect}"
89
+ raise "RPC is not enabled or reply_to is not included" if (@rpc_queue.nil? || @rpc_queue.name.nil?) && options[:reply_to].nil?
90
+ options[:timeout] ||= RPC_DEFAULT_TIMEOUT
91
+ options[:routing_key] ||= 'rcalls_queue'
92
+ options[:reply_to] ||= @rpc_queue.name
93
+ request = {
94
+ kind: kind,
95
+ payload: message,
96
+ options: {}
97
+ }
98
+ publish(request, options)
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,46 @@
1
+ require 'eventmachine'
2
+
3
+ module Combi
4
+ class Reactor
5
+ def self.start(&block)
6
+ EM::error_handler do |error|
7
+ STDERR << "ERROR IN EM\n"
8
+ STDERR << "\t#{error.inspect}"
9
+ STDERR << "\t#{error.backtrace.join("\t\n")}" << "\n"
10
+ end
11
+ log "-EM.start- the reactor is running: #{EM::reactor_running?}"
12
+ raise "EM did not shut down" if EM::reactor_running?
13
+ @@reactor_thread = Thread.new do
14
+ log "------- starting EM reactor"
15
+ EM::run do
16
+ log "------- reactor started"
17
+ Signal.trap("INT") { EM::stop_event_loop }
18
+ Signal.trap("TERM") { EM::stop_event_loop }
19
+ block.call unless block.nil?
20
+ end
21
+ log "------- reactor stopped"
22
+ end
23
+ 30.times do
24
+ sleep 0.1 unless EM::reactor_running?
25
+ end
26
+ end
27
+
28
+ def self.stop
29
+ log "-EM.stop- the reactor is running: #{EM::reactor_running?}"
30
+ EM::stop_event_loop if EM::reactor_running?
31
+ 50.times do
32
+ sleep 0.3 if EM::reactor_running?
33
+ end
34
+ end
35
+
36
+ def self.join_thread
37
+ @@reactor_thread.join if @@reactor_thread
38
+ end
39
+
40
+ def self.log(message)
41
+ return unless @debug_mode ||= ENV['DEBUG'] == 'true'
42
+ puts "#{object_id} #{self.class.name} #{message}"
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ require 'eventmachine'
2
+
3
+ module Combi
4
+ class ResponseStore
5
+ def initialize()
6
+ @waiters = {}
7
+ end
8
+
9
+ def add_waiter(key, waiter)
10
+ @waiters[key] = waiter
11
+ end
12
+
13
+ def handle_rpc_response(response)
14
+ correlation_id = response['correlation_id']
15
+ waiter = @waiters[correlation_id]
16
+ response = response['response']
17
+ waiter.succeed(response)
18
+ @waiters.delete correlation_id
19
+ end
20
+ end
21
+
22
+ class EventedWaiter
23
+ include EM::Deferrable
24
+
25
+ def self.wait_for(key, response_store, timeout)
26
+ waiter = new(key, response_store, timeout, Combi::Bus::RPC_MAX_POLLS)
27
+ response_store.add_waiter(key, waiter)
28
+ waiter
29
+ end
30
+
31
+ def initialize(key, response_store, timeout, max_polls)
32
+ self.timeout(timeout, RuntimeError.new(Timeout::Error))
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ module Combi
2
+ module Service
3
+
4
+ def setup(service_bus, context)
5
+ context ||= {}
6
+ context[:service_bus] = service_bus
7
+ setup_context(context)
8
+ setup_services
9
+ register_actions
10
+ end
11
+
12
+ def setup_context(context)
13
+ @context = context
14
+ @context.keys.each do |context_var|
15
+ define_singleton_method context_var do
16
+ @context[context_var]
17
+ end
18
+ end
19
+ end
20
+
21
+ def setup_services
22
+ end
23
+
24
+ def register_actions
25
+ actions.each do |handler|
26
+ service_bus.respond_to(self, handler)
27
+ end
28
+ fast_actions.each do |handler|
29
+ service_bus.respond_to(self, handler, fast: true)
30
+ end
31
+ end
32
+
33
+ def no_response
34
+ nil
35
+ end
36
+
37
+ def async_response(&block)
38
+ lambda &block
39
+ end
40
+
41
+ def actions
42
+ []
43
+ end
44
+
45
+ def fast_actions
46
+ []
47
+ end
48
+
49
+ def service_bus
50
+ @service_bus
51
+ end
52
+
53
+ def enable(*services, &block)
54
+ service_bus.enable(services)
55
+ yield block if block_given?
56
+ end
57
+
58
+ end
59
+ end