combi 0.1.0 → 0.2.0.pre
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/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
|