combi 0.1.0 → 0.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/combi/buses/bus.rb +88 -14
- data/lib/combi/buses/http.rb +10 -35
- data/lib/combi/buses/in_process.rb +12 -41
- data/lib/combi/buses/queue.rb +22 -35
- data/lib/combi/buses/web_socket.rb +29 -58
- data/lib/combi/queue_service.rb +8 -11
- data/lib/combi/response_store.rb +17 -30
- data/lib/combi/service.rb +8 -10
- data/lib/combi/version.rb +1 -1
- data/spec/integration/multi_bus_spec.rb +3 -3
- data/spec/lib/combi/buses/bus_spec.rb +27 -9
- data/spec/lib/combi/buses/web_socket_spec.rb +2 -2
- data/spec/lib/combi/service_spec.rb +1 -5
- data/spec/shared_examples/standard_bus.rb +24 -16
- data/spec/support/web_server.rb +6 -12
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac4f813928579df4ccf43c961482b6291268d32f
|
4
|
+
data.tar.gz: b27cb7008850b414d42d45b1b8f7d177d59e32f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0e6d420d29afb6f115273e2c17d9ef24b2ddec8084e53df4b3e10c9ab58c221ae966e87b495ba93c22095b188bf78f3ae0247d4d071141801ede2f56a3de795
|
7
|
+
data.tar.gz: 8699fe145b26e9d58ae734fc8d1b9c44d911ce46df719d61036e01cf60db7d402986befd22723fe91689ba0c780cfa83e162cd7966512de8a3065cfe08d509ac
|
data/lib/combi/buses/bus.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
require "combi/service"
|
2
2
|
require_relative 'correlation'
|
3
3
|
require 'yajl'
|
4
|
-
require 'yajl/json_gem' # for object.to_json, JSON.parse, etc...
|
5
4
|
|
6
5
|
module Combi
|
7
6
|
class Bus
|
8
|
-
attr_reader :services
|
9
|
-
|
10
7
|
RPC_DEFAULT_TIMEOUT = 1
|
8
|
+
attr_reader :routes
|
11
9
|
|
12
10
|
def initialize(options)
|
13
11
|
@options = options
|
14
|
-
@
|
12
|
+
@routes = {}
|
15
13
|
post_initialize
|
16
14
|
end
|
17
15
|
|
@@ -19,9 +17,14 @@ module Combi
|
|
19
17
|
end
|
20
18
|
|
21
19
|
def add_service(service_definition, options = {})
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
service_instance = make_service_instance(service_definition)
|
21
|
+
service_instance.actions.each do |service_name|
|
22
|
+
self.add_routes_for(service_name, service_instance)
|
23
|
+
end
|
24
|
+
service_instance.fast_actions.each do |service_name|
|
25
|
+
self.add_routes_for(service_name, service_instance, fast: true)
|
26
|
+
end
|
27
|
+
service_instance.setup(self, options[:context])
|
25
28
|
end
|
26
29
|
|
27
30
|
def start!
|
@@ -74,23 +77,94 @@ module Combi
|
|
74
77
|
service_class = Class.new do
|
75
78
|
include Combi::Service
|
76
79
|
include a_module
|
80
|
+
|
81
|
+
define_method :service_module do
|
82
|
+
a_module
|
83
|
+
end
|
84
|
+
protected :service_module
|
85
|
+
|
86
|
+
def remote_methods
|
87
|
+
@_REMOTE_METHODS ||= service_module.public_instance_methods(false) - [:actions]
|
88
|
+
end
|
89
|
+
def to_s
|
90
|
+
@_TO_S ||= "#{service_module.name || 'Annonymous'}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
service_class.new
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_routes_for(service_name, service_instance, options = {})
|
97
|
+
service_instance.remote_methods.each do |method|
|
98
|
+
add_route_for(service_name, method, service_instance, options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_route_for(service_name, action_name, service_instance, options = {})
|
103
|
+
path = [service_name, action_name].join('/')
|
104
|
+
puts "New route: #{path} :: #{service_instance}"
|
105
|
+
@routes[path] = {service_instance: service_instance, options: options}
|
106
|
+
end
|
107
|
+
|
108
|
+
# Funny name of a exception used to signal that the requested
|
109
|
+
# combination of service and method can not be found.
|
110
|
+
#
|
111
|
+
# Looked much funnier the first time... (@amuino)
|
112
|
+
class UnknownStop < RuntimeError
|
113
|
+
end
|
114
|
+
|
115
|
+
def resolve_route(service_name, kind)
|
116
|
+
path = [service_name, kind].join('/')
|
117
|
+
handler = @routes[path]
|
118
|
+
if handler
|
119
|
+
return service_instance = handler[:service_instance]
|
120
|
+
else
|
121
|
+
log "[WARNING] Service Path #{path} not found"
|
122
|
+
log "[WARNING] routes: #{@routes.keys.inspect}"
|
123
|
+
raise UnknownStop.new(path)
|
77
124
|
end
|
78
|
-
service = service_class.new
|
79
125
|
end
|
80
126
|
|
81
|
-
def invoke_service(
|
127
|
+
def invoke_service(service_name, action, params)
|
128
|
+
t0 = Time.now
|
129
|
+
path = "#{service_name}/#{action}?#{params.inspect}"
|
130
|
+
service_instance = resolve_route(service_name.to_s, action)
|
82
131
|
# convert keys to symbols in-place
|
83
132
|
params.keys.each {|key| params[key.to_sym] = params.delete(key) }
|
84
|
-
service_instance.send(action, params)
|
85
|
-
|
133
|
+
deferrable = sync_to_async service_instance.send(action, params)
|
134
|
+
deferrable.callback &log_service_invocation(true, t0, path)
|
135
|
+
deferrable.errback &log_service_invocation(false, t0, path)
|
136
|
+
deferrable
|
137
|
+
rescue StandardError => e
|
86
138
|
# TODO: report in a more effective way (I will not read server logs to find this)
|
87
139
|
require 'yaml'
|
88
140
|
puts " *** ERROR INVOKING SERVICE ***"
|
89
141
|
puts " - #{e.inspect}"
|
90
|
-
puts " - #{service_instance.class.ancestors.join ' > '}"
|
142
|
+
puts " - #{service_name} #{service_instance.class.ancestors.join ' > '}"
|
91
143
|
puts " - #{action}"
|
92
|
-
puts " - #{params.to_yaml}"
|
93
|
-
|
144
|
+
puts " - #{params.to_yaml.split("\n").join("\n\t")}"
|
145
|
+
# FIXME: strings because is what in_process tests expects
|
146
|
+
return sync_to_async error: { klass: e.class.name, message: e.message, backtrace: e.backtrace }
|
147
|
+
end
|
148
|
+
|
149
|
+
def sync_to_async(response)
|
150
|
+
return response if response.respond_to? :succeed # already a deferrable
|
151
|
+
deferrable = EM::DefaultDeferrable.new
|
152
|
+
if response.respond_to?(:keys) and response[:error]
|
153
|
+
deferrable.fail response[:error]
|
154
|
+
else
|
155
|
+
deferrable.succeed response
|
156
|
+
end
|
157
|
+
return deferrable
|
158
|
+
end
|
159
|
+
|
160
|
+
def log_service_invocation(success, t0, path)
|
161
|
+
Proc.new do |response|
|
162
|
+
result = success ? 'OK' : 'KO'
|
163
|
+
time = '%10.6fs' % (Time.now - t0)
|
164
|
+
base_msg = "#{result}\t#{time}\t#{path}"
|
165
|
+
base_msg += "\t#{response.inspect}" if success == false
|
166
|
+
puts base_msg
|
167
|
+
end
|
94
168
|
end
|
95
169
|
|
96
170
|
end
|
data/lib/combi/buses/http.rb
CHANGED
@@ -14,9 +14,9 @@ module Combi
|
|
14
14
|
def on_message(request)
|
15
15
|
path = request.path.split('/')
|
16
16
|
message = {
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
service_name: path[1],
|
18
|
+
kind: path[2],
|
19
|
+
payload: Yajl::Parser.parse(request.body, symbolize_keys: true)
|
20
20
|
}
|
21
21
|
@bus.on_message(message)
|
22
22
|
end
|
@@ -46,46 +46,21 @@ module Combi
|
|
46
46
|
@machine.on_message Rack::Request.new(env)
|
47
47
|
end
|
48
48
|
|
49
|
-
def on_message(
|
50
|
-
service_name
|
51
|
-
handler = handlers[service_name.to_s]
|
52
|
-
if handler
|
53
|
-
service_instance = handler[:service_instance]
|
54
|
-
kind = message['kind']
|
55
|
-
if service_instance.respond_to? kind
|
56
|
-
message['payload'] ||= {}
|
57
|
-
begin
|
58
|
-
response = invoke_service(service_instance, kind, message['payload'])
|
59
|
-
rescue RuntimeError => e
|
60
|
-
response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
|
61
|
-
end
|
62
|
-
{result: 'ok', response: response}
|
63
|
-
else
|
64
|
-
{result: 'error', response: {error: { klass: 'unknown action', message: kind } } }
|
65
|
-
end
|
66
|
-
else
|
67
|
-
{result: 'error', response: {error: 'unknown service'}}
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def respond_to(service_instance, action, options = {})
|
72
|
-
handlers[action.to_s] = {service_instance: service_instance, options: options}
|
73
|
-
end
|
74
|
-
|
75
|
-
def handlers
|
76
|
-
@handlers ||= {}
|
49
|
+
def on_message(service_name:, kind:, payload: {})
|
50
|
+
invoke_service(service_name, kind, payload)
|
77
51
|
end
|
78
52
|
|
79
53
|
def request(name, kind, message, options = {})
|
80
54
|
options[:timeout] ||= RPC_DEFAULT_TIMEOUT
|
81
55
|
|
82
56
|
correlation_id = Combi::Correlation.generate
|
83
|
-
waiter =
|
57
|
+
waiter = @response_store.wait_for(correlation_id, options[:timeout])
|
84
58
|
url = "#{@options[:remote_api]}#{name}/#{kind}"
|
85
|
-
|
59
|
+
message_json = Yajl::Encoder.encode(message)
|
60
|
+
request_async = EventMachine::HttpRequest.new(url, connection_timeout: options[:timeout]).post(body: message_json)
|
86
61
|
request_async.callback do |r|
|
87
|
-
parsed =
|
88
|
-
waiter.succeed(parsed
|
62
|
+
parsed = Yajl::Parser.parse(r.response, symbolize_keys: true)
|
63
|
+
waiter.succeed(parsed)
|
89
64
|
end
|
90
65
|
request_async.errback do |x|
|
91
66
|
waiter.fail(Timeout::Error.new)
|
@@ -3,53 +3,24 @@ require 'combi/buses/bus'
|
|
3
3
|
module Combi
|
4
4
|
class InProcess < Bus
|
5
5
|
|
6
|
-
def request(
|
6
|
+
def request(service_name, kind, message, options = {})
|
7
7
|
options[:timeout] ||= RPC_DEFAULT_TIMEOUT
|
8
|
-
handler = memory_handlers[handler_name.to_s]
|
9
8
|
waiter = EventMachine::DefaultDeferrable.new
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
response = invoke_service(service_instance, kind, message)
|
20
|
-
if response.respond_to? :succeed
|
21
|
-
response.callback do |service_response|
|
22
|
-
log "responding with deferred response: #{service_response.inspect[0..500]}"
|
23
|
-
waiter.succeed service_response
|
24
|
-
end
|
25
|
-
response.errback do |service_response|
|
26
|
-
failure_response = { 'error' => service_response }
|
27
|
-
log "responding with deferred failure: #{service_response.inspect[0..500]}"
|
28
|
-
waiter.fail(failure_response)
|
29
|
-
end
|
30
|
-
else
|
31
|
-
waiter.succeed response
|
32
|
-
end
|
33
|
-
end
|
34
|
-
rescue Timeout::Error => e
|
35
|
-
waiter.fail 'error' => 'Timeout::Error'
|
36
|
-
rescue RuntimeError => e
|
37
|
-
waiter.fail 'error' => {'klass' => e.class.name, 'message' => e.message, 'backtrace' => e.backtrace}
|
9
|
+
begin
|
10
|
+
Timeout.timeout(options[:timeout]) do
|
11
|
+
message = Yajl::Parser.parse(Yajl::Encoder.encode(message), symbolize_keys: true)
|
12
|
+
response = invoke_service(service_name, kind, message)
|
13
|
+
response.callback do |service_response|
|
14
|
+
waiter.succeed service_response
|
15
|
+
end
|
16
|
+
response.errback do |service_response|
|
17
|
+
waiter.fail error: service_response
|
38
18
|
end
|
39
|
-
else
|
40
|
-
waiter.fail('error' => { 'klass' => 'unknown action', 'message' => kind.to_s })
|
41
19
|
end
|
20
|
+
rescue Timeout::Error => e
|
21
|
+
waiter.fail error: 'Timeout::Error'
|
42
22
|
end
|
43
23
|
waiter
|
44
24
|
end
|
45
|
-
|
46
|
-
def respond_to(service_instance, action, options = {})
|
47
|
-
memory_handlers[action.to_s] = {service_instance: service_instance, options: options}
|
48
|
-
end
|
49
|
-
|
50
|
-
def memory_handlers
|
51
|
-
@memory_handlers ||= {}
|
52
|
-
end
|
53
|
-
|
54
25
|
end
|
55
26
|
end
|
data/lib/combi/buses/queue.rb
CHANGED
@@ -25,8 +25,13 @@ module Combi
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def add_routes_for(service_name, service_instance, options = {})
|
29
|
+
create_queue_for_service(service_name, options)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_queue_for_service(service_name, options = {})
|
34
|
+
log "creating queue #{service_name}"
|
30
35
|
queue_options = {}
|
31
36
|
subscription_options = {}
|
32
37
|
if options[:fast] == true
|
@@ -35,10 +40,10 @@ module Combi
|
|
35
40
|
subscription_options[:ack] = true
|
36
41
|
end
|
37
42
|
queue_service.ready do
|
38
|
-
queue_service.queue(
|
39
|
-
log "subscribing to queue #{
|
43
|
+
queue_service.queue(service_name.to_s, queue_options) do |queue|
|
44
|
+
log "subscribing to queue #{service_name.to_s} with options #{queue_options}"
|
40
45
|
queue.subscribe(subscription_options) do |delivery_info, payload|
|
41
|
-
respond
|
46
|
+
respond service_name, payload, delivery_info
|
42
47
|
queue_service.acknowledge delivery_info unless options[:fast] == true
|
43
48
|
end
|
44
49
|
end
|
@@ -51,7 +56,7 @@ module Combi
|
|
51
56
|
options[:routing_key] = name.to_s
|
52
57
|
correlation_id = Combi::Correlation.generate
|
53
58
|
options[:correlation_id] = correlation_id
|
54
|
-
waiter =
|
59
|
+
waiter = @response_store.wait_for(correlation_id, options[:timeout])
|
55
60
|
queue_service.next_ready_only do
|
56
61
|
log "Making request: #{name}.#{kind} #{message.inspect[0..500]}\t|| #{options.inspect[0..500]}"
|
57
62
|
queue_service.call(kind, message, options)
|
@@ -59,36 +64,18 @@ module Combi
|
|
59
64
|
waiter
|
60
65
|
end
|
61
66
|
|
62
|
-
def respond(
|
63
|
-
message =
|
64
|
-
kind = message[
|
65
|
-
payload = message[
|
66
|
-
options = message[
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
response = invoke_service(service_instance, kind, payload)
|
71
|
-
rescue RuntimeError => e
|
72
|
-
response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
|
73
|
-
end
|
74
|
-
else
|
75
|
-
log "Service instance does not respond to #{kind}: #{service_instance.inspect}"
|
76
|
-
response = {error: { klass: 'unknown action', message: kind } }
|
67
|
+
def respond(service_name, request, delivery_info)
|
68
|
+
message = Yajl::Parser.parse request, symbolize_keys: true
|
69
|
+
kind = message[:kind]
|
70
|
+
payload = message[:payload]
|
71
|
+
options = message[:options]
|
72
|
+
response = invoke_service(service_name, kind, payload)
|
73
|
+
response.callback do |service_response|
|
74
|
+
queue_service.respond service_response, delivery_info
|
77
75
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
log "responding with deferred answer: #{service_response.inspect[0..500]}"
|
82
|
-
queue_service.respond(service_response.to_json, delivery_info)
|
83
|
-
end
|
84
|
-
response.errback do |service_response|
|
85
|
-
failure_response = { error: service_response }
|
86
|
-
log "responding with deferred failure: #{service_response.inspect[0..500]}"
|
87
|
-
queue_service.respond(failure_response.to_json, delivery_info)
|
88
|
-
end
|
89
|
-
else
|
90
|
-
log "responding with inmediate answer: #{response.inspect[0..500]}"
|
91
|
-
queue_service.respond(response.to_json, delivery_info)
|
76
|
+
response.errback do |service_response|
|
77
|
+
failure_response = { error: service_response }
|
78
|
+
queue_service.respond failure_response, delivery_info
|
92
79
|
end
|
93
80
|
end
|
94
81
|
|
@@ -23,8 +23,8 @@ module Combi
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def on_message(ws, session, raw_message)
|
26
|
-
@bus.log "ON MESSAGE #{raw_message[0..500]}"
|
27
|
-
message =
|
26
|
+
@bus.log "WS SERVER ON MESSAGE #{raw_message[0..500]}"
|
27
|
+
message = Yajl::Parser.parse raw_message, symbolize_keys: true
|
28
28
|
@bus.on_message(ws, message, session)
|
29
29
|
end
|
30
30
|
|
@@ -73,7 +73,7 @@ module Combi
|
|
73
73
|
|
74
74
|
ws.on :message do |event|
|
75
75
|
@bus.log "ON MESSAGE: #{event.data[0..500]}"
|
76
|
-
message =
|
76
|
+
message = Yajl::Parser.parse event.data, symbolize_keys: true
|
77
77
|
@bus.on_message(ws, message)
|
78
78
|
end
|
79
79
|
|
@@ -96,11 +96,10 @@ module Combi
|
|
96
96
|
|
97
97
|
end
|
98
98
|
|
99
|
-
attr_reader :
|
99
|
+
attr_reader :ready
|
100
100
|
|
101
101
|
def initialize(options)
|
102
102
|
super
|
103
|
-
@handlers = {}
|
104
103
|
end
|
105
104
|
|
106
105
|
def post_initialize
|
@@ -161,80 +160,52 @@ module Combi
|
|
161
160
|
end
|
162
161
|
|
163
162
|
def on_message(ws, message, session = nil)
|
164
|
-
if message[
|
163
|
+
if message[:correlation_id] && message.has_key?(:result)
|
165
164
|
@response_store.handle_rpc_response(message)
|
166
|
-
log "
|
165
|
+
log "Handled response with correlation_id #{message[:correlation_id]} - #{message.inspect[0..500]}"
|
167
166
|
return
|
168
167
|
end
|
169
|
-
service_name = message[
|
170
|
-
kind = message[
|
171
|
-
payload = message[
|
172
|
-
payload[
|
168
|
+
service_name = message[:service]
|
169
|
+
kind = message[:kind]
|
170
|
+
payload = message[:payload] || {}
|
171
|
+
payload[:session] = session
|
173
172
|
begin
|
174
|
-
response =
|
173
|
+
response = invoke_service(service_name, kind, payload)
|
175
174
|
rescue RuntimeError => e
|
176
175
|
response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
|
177
176
|
end
|
178
177
|
|
179
|
-
msg = {result: 'ok', correlation_id: message[
|
178
|
+
msg = {result: 'ok', correlation_id: message[:correlation_id]}
|
180
179
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
msg[:response] = service_response.to_json
|
186
|
-
ws.send(msg.to_json)
|
187
|
-
end
|
188
|
-
response.errback do |service_response|
|
189
|
-
log "responding with deferred error: #{service_response.inspect[0..500]}"
|
190
|
-
error_response = { error: service_response }
|
191
|
-
msg[:response] = error_response.to_json
|
192
|
-
ws.send(msg.to_json)
|
193
|
-
end
|
194
|
-
else
|
195
|
-
log "responding with inmediate answer: #{response.inspect[0..500]}"
|
196
|
-
msg[:response] = response.to_json
|
197
|
-
ws.send(msg.to_json)
|
180
|
+
response.callback do |service_response|
|
181
|
+
msg[:response] = service_response
|
182
|
+
serialized = Yajl::Encoder.encode msg
|
183
|
+
ws.send serialized
|
198
184
|
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
if handler
|
204
|
-
service_instance = handler[:service_instance]
|
205
|
-
if service_instance.respond_to? kind
|
206
|
-
response = invoke_service(service_instance, kind, payload)
|
207
|
-
else
|
208
|
-
log "[WARNING] Service #{service_name}(#{service_instance.class.name}) does not respond to message #{kind}"
|
209
|
-
response = {error: { klass: 'unknown action', message: kind } }
|
210
|
-
end
|
211
|
-
else
|
212
|
-
log "[WARNING] Service #{service_name} not found"
|
213
|
-
log "[WARNING] handlers: #{handlers.keys.inspect}"
|
214
|
-
response = {error: 'unknown service'}
|
185
|
+
response.errback do |service_response|
|
186
|
+
msg[:response] = {error: service_response}
|
187
|
+
serialized = Yajl::Encoder.encode msg
|
188
|
+
ws.send serialized
|
215
189
|
end
|
216
190
|
end
|
217
191
|
|
218
|
-
def respond_to(service_instance, action, options = {})
|
219
|
-
log "registering #{action}"
|
220
|
-
handlers[action.to_s] = {service_instance: service_instance, options: options}
|
221
|
-
log "handlers: #{handlers.keys.inspect}"
|
222
|
-
end
|
223
|
-
|
224
192
|
def request(name, kind, message, options = {})
|
225
193
|
options[:timeout] ||= RPC_DEFAULT_TIMEOUT
|
194
|
+
correlation_id = Combi::Correlation.generate
|
226
195
|
msg = {
|
227
196
|
service: name,
|
228
197
|
kind: kind,
|
229
|
-
payload: message
|
198
|
+
payload: message,
|
199
|
+
correlation_id: correlation_id
|
230
200
|
}
|
231
|
-
|
232
|
-
msg[:correlation_id] = correlation_id
|
233
|
-
waiter = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
|
201
|
+
waiter = @response_store.wait_for(correlation_id, options[:timeout])
|
234
202
|
@ready.callback do |r|
|
235
203
|
web_socket = @machine.ws || options[:ws]
|
236
|
-
|
237
|
-
|
204
|
+
unless web_socket.nil?
|
205
|
+
serialized = Yajl::Encoder.encode msg
|
206
|
+
log "sending request #{serialized}"
|
207
|
+
web_socket.send serialized
|
208
|
+
end
|
238
209
|
end
|
239
210
|
waiter
|
240
211
|
end
|
data/lib/combi/queue_service.rb
CHANGED
@@ -47,7 +47,7 @@ module Combi
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def connect(config, &after_connect)
|
50
|
-
puts "[INFO] trying to connect to queue"
|
50
|
+
puts "[INFO] trying to connect to queue server"
|
51
51
|
@amqp_conn = AMQP.connect(config) do |connection, open_ok|
|
52
52
|
@channel = AMQP::Channel.new @amqp_conn
|
53
53
|
@channel.auto_recovery = true
|
@@ -94,8 +94,8 @@ module Combi
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def respond(response, delivery_info)
|
97
|
-
|
98
|
-
publish
|
97
|
+
serialized = Yajl::Encoder.encode response
|
98
|
+
publish serialized, routing_key: delivery_info.reply_to, correlation_id: delivery_info.correlation_id
|
99
99
|
end
|
100
100
|
|
101
101
|
def create_rpc_queue
|
@@ -103,9 +103,10 @@ module Combi
|
|
103
103
|
@rpc_queue = queue('', exclusive: true, auto_delete: true) do |rpc_queue|
|
104
104
|
log "\tRPC QUEUE: #{@rpc_queue.name}"
|
105
105
|
rpc_queue.subscribe do |metadata, response|
|
106
|
+
parsed_response = Yajl::Parser.parse response, symbolize_keys: true
|
106
107
|
message = {
|
107
|
-
|
108
|
-
|
108
|
+
correlation_id: metadata.correlation_id,
|
109
|
+
response: parsed_response
|
109
110
|
}
|
110
111
|
rpc_callback.call(message) unless rpc_callback.nil?
|
111
112
|
end
|
@@ -119,12 +120,8 @@ module Combi
|
|
119
120
|
options[:expiration] = ((options[:timeout] || RPC_DEFAULT_TIMEOUT) * 1000).to_i
|
120
121
|
options[:routing_key] ||= 'rcalls_queue'
|
121
122
|
options[:reply_to] ||= @rpc_queue.name
|
122
|
-
request = {
|
123
|
-
|
124
|
-
payload: message,
|
125
|
-
options: {}
|
126
|
-
}
|
127
|
-
publish(request.to_json, options)
|
123
|
+
request = Yajl::Encoder.encode kind: kind, payload: message, options: {}
|
124
|
+
publish request, options
|
128
125
|
end
|
129
126
|
|
130
127
|
end
|
data/lib/combi/response_store.rb
CHANGED
@@ -6,49 +6,36 @@ module Combi
|
|
6
6
|
@waiters = {}
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
waiter
|
12
|
-
|
9
|
+
# Returns an EM::Deferrable
|
10
|
+
def wait_for(correlation_id, timeout)
|
11
|
+
waiter = EM::DefaultDeferrable.new
|
12
|
+
add_waiter correlation_id, waiter
|
13
|
+
waiter.timeout timeout, error: 'Timeout::Error'
|
13
14
|
end
|
14
15
|
|
15
16
|
def handle_rpc_response(response)
|
16
|
-
correlation_id = response[
|
17
|
+
correlation_id = response[:correlation_id]
|
17
18
|
waiter = @waiters[correlation_id]
|
18
19
|
return unless waiter
|
19
|
-
response =
|
20
|
-
if response.respond_to?(:keys) and response[
|
21
|
-
waiter.fail
|
20
|
+
response = response[:response] #Yajl::Parser.parse response[:response], sybolize_keys: true
|
21
|
+
if response.respond_to?(:keys) and response[:error]
|
22
|
+
waiter.fail response
|
22
23
|
else
|
23
|
-
waiter.succeed
|
24
|
+
waiter.succeed response
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
@waiters.delete key
|
29
|
-
end
|
30
|
-
end
|
28
|
+
protected
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
puts "#{Time.now.to_f} #{self.name} #{message}"
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.wait_for(key, response_store, timeout)
|
40
|
-
t1 = Time.now
|
41
|
-
log "started waiting for #{key}"
|
42
|
-
waiter = new(key, response_store, timeout)
|
43
|
-
response_store.add_waiter(key, waiter)
|
44
|
-
waiter.callback {|r| log "success waiting for #{key}: #{Time.now.to_f - t1.to_f}s" }
|
45
|
-
waiter.errback {|r| log "failed waiting for #{key}: #{Time.now.to_f - t1.to_f}s, #{r.inspect[0..500]}" }
|
30
|
+
def add_waiter(correlation_id, waiter)
|
31
|
+
@waiters[correlation_id] = waiter
|
32
|
+
waiter.callback { |r| remove_waiter correlation_id }
|
33
|
+
waiter.errback { |r| remove_waiter correlation_id }
|
46
34
|
waiter
|
47
35
|
end
|
48
36
|
|
49
|
-
def
|
50
|
-
|
37
|
+
def remove_waiter(correlation_id)
|
38
|
+
@waiters.delete correlation_id
|
51
39
|
end
|
52
|
-
|
53
40
|
end
|
54
41
|
end
|
data/lib/combi/service.rb
CHANGED
@@ -6,7 +6,6 @@ module Combi
|
|
6
6
|
context[:service_bus] = service_bus
|
7
7
|
setup_context(context)
|
8
8
|
setup_services
|
9
|
-
register_actions
|
10
9
|
end
|
11
10
|
|
12
11
|
def setup_context(context)
|
@@ -21,15 +20,6 @@ module Combi
|
|
21
20
|
def setup_services
|
22
21
|
end
|
23
22
|
|
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
23
|
def no_response
|
34
24
|
nil
|
35
25
|
end
|
@@ -46,6 +36,10 @@ module Combi
|
|
46
36
|
[]
|
47
37
|
end
|
48
38
|
|
39
|
+
def remote_methods
|
40
|
+
@_REMOTE_METHODS ||= public_methods(false) - Combi::Service.public_instance_methods
|
41
|
+
end
|
42
|
+
|
49
43
|
def service_bus
|
50
44
|
@service_bus
|
51
45
|
end
|
@@ -55,5 +49,9 @@ module Combi
|
|
55
49
|
yield block if block_given?
|
56
50
|
end
|
57
51
|
|
52
|
+
def to_s
|
53
|
+
@_TO_S ||= "#{self.class.name}"
|
54
|
+
end
|
55
|
+
|
58
56
|
end
|
59
57
|
end
|
data/lib/combi/version.rb
CHANGED
@@ -36,7 +36,7 @@ describe "in a multi bus environment" do
|
|
36
36
|
defer.fail('unknown error')
|
37
37
|
end
|
38
38
|
else
|
39
|
-
if service_result.respond_to?(:keys) && service_result[
|
39
|
+
if service_result.respond_to?(:keys) && service_result[:error]
|
40
40
|
defer.fail('other service failed')
|
41
41
|
else
|
42
42
|
defer.succeed(service_result*2)
|
@@ -173,7 +173,7 @@ describe "in a multi bus environment" do
|
|
173
173
|
prepare
|
174
174
|
EM.synchrony do
|
175
175
|
service_result = EM::Synchrony.sync main_bus_consumer.request(:say_hello_if_you_can, :do_it, params, options)
|
176
|
-
service_result[
|
176
|
+
service_result[:error][:message].should eq "I can't talk"
|
177
177
|
done
|
178
178
|
finalize
|
179
179
|
end
|
@@ -191,7 +191,7 @@ describe "in a multi bus environment" do
|
|
191
191
|
EM.synchrony do
|
192
192
|
params.merge!('service_name' => 'say_hello_if_you_can')
|
193
193
|
service_result = EM::Synchrony.sync main_bus_consumer.request(:repeat_with_me, :do_it, params, options)
|
194
|
-
service_result[
|
194
|
+
service_result[:error].should eq 'other service failed'
|
195
195
|
done
|
196
196
|
finalize
|
197
197
|
end
|
@@ -1,22 +1,40 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'combi/buses/bus'
|
2
3
|
|
3
4
|
describe 'Combi::Bus' do
|
4
5
|
context 'registers services' do
|
5
|
-
Given(:
|
6
|
+
Given(:bus) { Combi::Bus.new({}) }
|
6
7
|
|
7
8
|
context 'via instances' do
|
8
|
-
Given(:service_class)
|
9
|
+
Given(:service_class) do
|
10
|
+
Class.new do
|
11
|
+
include Combi::Service
|
12
|
+
def actions; ['class']; end
|
13
|
+
def remote; end
|
14
|
+
end
|
15
|
+
end
|
9
16
|
Given(:service_definition) { service_class.new }
|
10
|
-
|
11
|
-
|
17
|
+
Given(:path) { 'class/remote' }
|
18
|
+
When { bus.add_service service_definition }
|
19
|
+
Then { bus.routes.keys.length == 1 }
|
20
|
+
And { bus.routes[path][:service_instance] == service_definition }
|
12
21
|
end
|
13
22
|
|
14
23
|
context 'via modules' do
|
15
|
-
Given(:service_definition)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
Given(:service_definition) do
|
25
|
+
Module.new do
|
26
|
+
def actions; ['module']; end
|
27
|
+
def remote; end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
Given(:path) { 'module/remote' }
|
31
|
+
When { bus.add_service service_definition
|
32
|
+
puts bus.routes.inspect
|
33
|
+
puts bus.routes[path].inspect
|
34
|
+
puts bus.routes.keys.inspect
|
35
|
+
}
|
36
|
+
Then { bus.routes.keys.length == 1 }
|
37
|
+
And { bus.routes[path][:service_instance].is_a? service_definition }
|
20
38
|
end
|
21
39
|
end
|
22
40
|
end
|
@@ -94,8 +94,8 @@ describe 'Combi::WebSocket' do
|
|
94
94
|
provider.stop!
|
95
95
|
service_result = EM::Synchrony.sync consumer.request(:null, :do_it, {}, { timeout: 0.3 })
|
96
96
|
service_result.should be_a Hash
|
97
|
-
service_result.should have_key
|
98
|
-
service_result[
|
97
|
+
service_result.should have_key :error
|
98
|
+
service_result[:error].should eq 'Timeout::Error'
|
99
99
|
done(0.3) #timeout response must came before this timeout
|
100
100
|
end
|
101
101
|
provider.stop!
|
@@ -9,19 +9,15 @@ describe 'Combi::Service' do
|
|
9
9
|
Given!("setup methods are stubbed") do
|
10
10
|
subject.stub(:setup_context).and_call_original
|
11
11
|
subject.stub(:setup_services).and_call_original
|
12
|
-
subject.stub(:register_actions).and_call_original
|
13
12
|
end
|
14
13
|
Given(:the_bus) { double "the bus" }
|
15
14
|
When { subject.setup the_bus, context }
|
16
15
|
context 'sets up the context' do
|
17
|
-
Then { subject.should have_received
|
16
|
+
Then { subject.should have_received(:setup_context).with context }
|
18
17
|
end
|
19
18
|
context 'allows the service to setup itself' do
|
20
19
|
Then { subject.should have_received :setup_services }
|
21
20
|
end
|
22
|
-
context 'registers the advertised actions' do
|
23
|
-
Then { subject.should have_received :register_actions }
|
24
|
-
end
|
25
21
|
context 'always includes the service_bus in the context' do
|
26
22
|
Then { subject.service_bus == the_bus }
|
27
23
|
end
|
@@ -69,8 +69,8 @@ shared_examples_for "standard_bus" do
|
|
69
69
|
EM.synchrony do
|
70
70
|
service_result = EM::Synchrony.sync consumer.request(:sleep, :do_it, params, { timeout: time_base/2.0 })
|
71
71
|
service_result.should be_a Hash
|
72
|
-
service_result.should have_key
|
73
|
-
service_result[
|
72
|
+
service_result.should have_key :error
|
73
|
+
service_result[:error].should eq 'Timeout::Error'
|
74
74
|
done(time_base) #timeout response must came before this timeout
|
75
75
|
end
|
76
76
|
finalize
|
@@ -114,8 +114,15 @@ shared_examples_for "standard_bus" do
|
|
114
114
|
end
|
115
115
|
|
116
116
|
context 'for maps' do
|
117
|
-
|
118
|
-
|
117
|
+
context 'with string keys' do
|
118
|
+
Given(:data) { {'a' => 1, 'b' => 'dos'} }
|
119
|
+
Then { result_container[:result].keys.should eq data.keys.map(&:to_sym)}
|
120
|
+
And { result_container[:result].values.should eq data.values }
|
121
|
+
end
|
122
|
+
context 'with symbol keys' do
|
123
|
+
Given(:data) { {a: 1, b: 'dos'} }
|
124
|
+
Then { result_container[:result].should eq data}
|
125
|
+
end
|
119
126
|
end
|
120
127
|
|
121
128
|
context 'for objects returns their json version' do
|
@@ -131,7 +138,7 @@ shared_examples_for "standard_bus" do
|
|
131
138
|
end
|
132
139
|
end
|
133
140
|
Given(:data) { custom_class.new(custom_json).to_json}
|
134
|
-
Then { result_container[:result].should eq
|
141
|
+
Then { result_container[:result].should eq custom_json}
|
135
142
|
end
|
136
143
|
end
|
137
144
|
|
@@ -143,10 +150,10 @@ shared_examples_for "standard_bus" do
|
|
143
150
|
prepare
|
144
151
|
EM.synchrony do
|
145
152
|
service_result = EM::Synchrony.sync consumer.request(:shout_error, :do_it, {message: error_message}, { timeout: 0.1 })
|
146
|
-
service_result[
|
147
|
-
service_result[
|
148
|
-
service_result[
|
149
|
-
service_result[
|
153
|
+
service_result[:error].should be_a Hash
|
154
|
+
service_result[:error][:message].should eq error_message
|
155
|
+
service_result[:error][:backtrace].should_not be_nil
|
156
|
+
service_result[:error][:backtrace].should be_an Array
|
150
157
|
done
|
151
158
|
finalize
|
152
159
|
end
|
@@ -155,7 +162,7 @@ shared_examples_for "standard_bus" do
|
|
155
162
|
end
|
156
163
|
|
157
164
|
context 'return an error when requesting an unknown service' do
|
158
|
-
Given(:error_message) {
|
165
|
+
Given(:error_message) { Combi::Bus::UnknownStop.name }
|
159
166
|
When(:service) { provider.add_service broken_service }
|
160
167
|
Then do
|
161
168
|
em do
|
@@ -163,10 +170,11 @@ shared_examples_for "standard_bus" do
|
|
163
170
|
EM.synchrony do
|
164
171
|
begin
|
165
172
|
service_result = EM::Synchrony.sync consumer.request(:some_not_service, :do_it, {}, { timeout: 0.1 })
|
166
|
-
if consumer.class == Combi::Queue
|
167
|
-
service_result[
|
173
|
+
if defined?(Combi::Queue) and consumer.class == Combi::Queue
|
174
|
+
service_result[:error].should eq "Timeout::Error"
|
168
175
|
else
|
169
|
-
service_result[
|
176
|
+
service_result[:error][:klass].should eq error_message
|
177
|
+
service_result[:error][:message].should eq 'some_not_service/do_it'
|
170
178
|
end
|
171
179
|
done
|
172
180
|
finalize
|
@@ -177,7 +185,7 @@ shared_examples_for "standard_bus" do
|
|
177
185
|
end
|
178
186
|
|
179
187
|
context 'return an error when requesting an unknown action for the service' do
|
180
|
-
Given(:error_message) {
|
188
|
+
Given(:error_message) { Combi::Bus::UnknownStop.name }
|
181
189
|
When(:service) { provider.add_service echo_service }
|
182
190
|
Then do
|
183
191
|
em do
|
@@ -185,8 +193,8 @@ shared_examples_for "standard_bus" do
|
|
185
193
|
EM.synchrony do
|
186
194
|
begin
|
187
195
|
service_result = EM::Synchrony.sync consumer.request(:echo_this, :do_other, {}, { timeout: 0.1 })
|
188
|
-
service_result[
|
189
|
-
service_result[
|
196
|
+
service_result[:error][:klass].should eq error_message
|
197
|
+
service_result[:error][:message].should eq 'echo_this/do_other'
|
190
198
|
done
|
191
199
|
finalize
|
192
200
|
end
|
data/spec/support/web_server.rb
CHANGED
@@ -8,15 +8,17 @@ class DeferrableBody
|
|
8
8
|
def initialize(defer)
|
9
9
|
@defer = defer
|
10
10
|
@defer.callback do |service_response|
|
11
|
+
json_response = Yajl::Encoder.encode service_response
|
11
12
|
EM::next_tick do
|
12
|
-
self.call [
|
13
|
+
self.call [json_response]
|
13
14
|
self.succeed
|
14
15
|
end
|
15
16
|
end
|
16
17
|
@defer.errback do |service_response|
|
17
18
|
error_response = { error: service_response }
|
19
|
+
json_response = Yajl::Encoder.encode error_response
|
18
20
|
EM::next_tick do
|
19
|
-
self.call [
|
21
|
+
self.call [json_response]
|
20
22
|
self.fail
|
21
23
|
end
|
22
24
|
end
|
@@ -38,16 +40,8 @@ def start_web_server(http_bus, port, webserver = 'thin')
|
|
38
40
|
require webserver
|
39
41
|
app = lambda do |env|
|
40
42
|
response_message = http_bus.manage_request(env)
|
41
|
-
|
42
|
-
|
43
|
-
AsyncResponse
|
44
|
-
else
|
45
|
-
response_rack = Rack::Response.new
|
46
|
-
response_rack.status = response_message.nil? ? 201 : 200
|
47
|
-
response_rack.body = [response_message.to_json]
|
48
|
-
response_rack.finish
|
49
|
-
response_rack
|
50
|
-
end
|
43
|
+
env['async.callback'].call [200, {}, DeferrableBody.new(response_message)]
|
44
|
+
AsyncResponse
|
51
45
|
end
|
52
46
|
Thin::Logging.silent = true if webserver == 'thin'
|
53
47
|
rack_handler = Rack::Handler.get(webserver)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: combi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- German Del Zotto
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-07-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: yajl-ruby
|
@@ -191,9 +191,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
191
191
|
version: '0'
|
192
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
193
|
requirements:
|
194
|
-
- - "
|
194
|
+
- - ">"
|
195
195
|
- !ruby/object:Gem::Version
|
196
|
-
version:
|
196
|
+
version: 1.3.1
|
197
197
|
requirements: []
|
198
198
|
rubyforge_project:
|
199
199
|
rubygems_version: 2.3.0
|