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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 28565637d9bdb76e4bd9f256be6af0c89900ec25
4
- data.tar.gz: 411ec39864499243853f42677ec76f3708c8c630
3
+ metadata.gz: ac4f813928579df4ccf43c961482b6291268d32f
4
+ data.tar.gz: b27cb7008850b414d42d45b1b8f7d177d59e32f9
5
5
  SHA512:
6
- metadata.gz: 8a353643e81e81912a88a8b756cf29a12ba19982e6db5e09c34fd88d153041bf14434a6d609196c202336d99671f08f3bf49b9c1fde43b1f656c04b2b66b9fa2
7
- data.tar.gz: 98151bf6b13683a2992a3e0078bfea21f136377fd33971bb5190893aaac1e3e63b1ff9940516ae2558959229eb36e2d668890630b728850424e1467cf0e27d1a
6
+ metadata.gz: e0e6d420d29afb6f115273e2c17d9ef24b2ddec8084e53df4b3e10c9ab58c221ae966e87b495ba93c22095b188bf78f3ae0247d4d071141801ede2f56a3de795
7
+ data.tar.gz: 8699fe145b26e9d58ae734fc8d1b9c44d911ce46df719d61036e01cf60db7d402986befd22723fe91689ba0c780cfa83e162cd7966512de8a3065cfe08d509ac
@@ -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
- @services = []
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
- service = make_service_instance(service_definition)
23
- service.setup(self, options[:context])
24
- @services << service
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(service_instance, action, params)
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
- rescue => e
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
- raise e
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
@@ -14,9 +14,9 @@ module Combi
14
14
  def on_message(request)
15
15
  path = request.path.split('/')
16
16
  message = {
17
- "service" => path[1],
18
- "kind" => path[2],
19
- "payload" => JSON.parse(request.body)
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(message)
50
- service_name = message['service']
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 = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
57
+ waiter = @response_store.wait_for(correlation_id, options[:timeout])
84
58
  url = "#{@options[:remote_api]}#{name}/#{kind}"
85
- request_async = EventMachine::HttpRequest.new(url, connection_timeout: options[:timeout]).post(body: message.to_json)
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 = JSON.parse(r.response)
88
- waiter.succeed(parsed['response'])
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(handler_name, kind, message, options = {})
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
- if handler.nil?
11
- waiter.fail('error' => 'unknown service')
12
- else
13
- service_instance = handler[:service_instance]
14
- message = JSON.parse(message.to_json)
15
- if service_instance.respond_to?(kind)
16
- waiter.timeout(options[:timeout], 'error' => 'Timeout::Error')
17
- begin
18
- Timeout.timeout(options[:timeout]) do
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
@@ -25,8 +25,13 @@ module Combi
25
25
  end
26
26
  end
27
27
 
28
- def respond_to(service_instance, action, options = {})
29
- log "registering #{action}"
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(action.to_s, queue_options) do |queue|
39
- log "subscribing to queue #{action.to_s} with options #{queue_options}"
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 service_instance, payload, delivery_info
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 = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
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(service_instance, request, delivery_info)
63
- message = JSON.parse request
64
- kind = message['kind']
65
- payload = message['payload']
66
- options = message['options']
67
- if service_instance.respond_to?(kind)
68
- log "generating response for #{service_instance.class}#{service_instance.actions.inspect}.#{kind} #{payload.inspect[0..500]}"
69
- begin
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
- if response.respond_to? :succeed
79
- log "response is deferred"
80
- response.callback do |service_response|
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 = JSON.parse(raw_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 = JSON.parse(event.data)
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 :handlers, :ready
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['correlation_id'] && message.has_key?('result')
163
+ if message[:correlation_id] && message.has_key?(:result)
165
164
  @response_store.handle_rpc_response(message)
166
- log "Stored message with correlation_id #{message['correlation_id']} - #{message.inspect[0..500]}"
165
+ log "Handled response with correlation_id #{message[:correlation_id]} - #{message.inspect[0..500]}"
167
166
  return
168
167
  end
169
- service_name = message['service']
170
- kind = message['kind']
171
- payload = message['payload'] || {}
172
- payload['session'] = session
168
+ service_name = message[:service]
169
+ kind = message[:kind]
170
+ payload = message[:payload] || {}
171
+ payload[:session] = session
173
172
  begin
174
- response = lookup_and_invoke_service(service_name, kind, payload)
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['correlation_id']}
178
+ msg = {result: 'ok', correlation_id: message[:correlation_id]}
180
179
 
181
- if response.respond_to? :succeed
182
- log "response is deferred"
183
- response.callback do |service_response|
184
- log "responding with deferred answer: #{service_response.inspect[0..500]}"
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
- end
200
-
201
- def lookup_and_invoke_service(service_name, kind, payload)
202
- handler = handlers[service_name.to_s]
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
- correlation_id = Combi::Correlation.generate
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
- log "sending request #{msg.inspect}"
237
- web_socket.send msg.to_json unless web_socket.nil?
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
@@ -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
- response = response.call if response.respond_to? :call
98
- publish response, routing_key: delivery_info.reply_to, correlation_id: delivery_info.correlation_id
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
- 'correlation_id' => metadata.correlation_id,
108
- 'response' => response
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
- kind: kind,
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
@@ -6,49 +6,36 @@ module Combi
6
6
  @waiters = {}
7
7
  end
8
8
 
9
- def add_waiter(key, waiter)
10
- @waiters[key] = waiter
11
- waiter.callback { |r| finish key }
12
- waiter.errback { |r| finish key }
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['correlation_id']
17
+ correlation_id = response[:correlation_id]
17
18
  waiter = @waiters[correlation_id]
18
19
  return unless waiter
19
- response = JSON.parse response['response']
20
- if response.respond_to?(:keys) and response['error']
21
- waiter.fail(response)
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(response)
24
+ waiter.succeed response
24
25
  end
25
26
  end
26
27
 
27
- def finish(key)
28
- @waiters.delete key
29
- end
30
- end
28
+ protected
31
29
 
32
- class EventedWaiter
33
- include EM::Deferrable
34
- def self.log(message)
35
- return unless @debug_mode ||= ENV['DEBUG'] == 'true'
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 initialize(key, response_store, timeout)
50
- self.timeout(timeout, 'error' => 'Timeout::Error')
37
+ def remove_waiter(correlation_id)
38
+ @waiters.delete correlation_id
51
39
  end
52
-
53
40
  end
54
41
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Combi
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0.pre'
3
3
  end
@@ -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['error']
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['error']['message'].should eq "I can't talk"
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['error'].should eq 'other service failed'
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(:subject) { Combi::Bus.new({}) }
6
+ Given(:bus) { Combi::Bus.new({}) }
6
7
 
7
8
  context 'via instances' do
8
- Given(:service_class) { Class.new{include Combi::Service} }
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
- When { subject.add_service service_definition }
11
- Then { subject.services == [service_definition] }
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) { Module.new }
16
- When { subject.add_service service_definition }
17
- Then { subject.services.length == 1 }
18
- And { Combi::Service === subject.services[0] }
19
- And { service_definition === subject.services[0] }
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 'error'
98
- service_result['error'].should eq 'Timeout::Error'
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 :setup_context }
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 'error'
73
- service_result['error'].should eq 'Timeout::Error'
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
- Given(:data) { {'a' => 1, 'b' => 'dos'} }
118
- Then { result_container[:result].should eq data}
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 JSON.parse(custom_json.to_json)}
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['error'].should be_a Hash
147
- service_result['error']['message'].should eq error_message
148
- service_result['error']['backtrace'].should_not be_nil
149
- service_result['error']['backtrace'].should be_an Array
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) { 'unknown service' }
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['error'].should eq "Timeout::Error"
173
+ if defined?(Combi::Queue) and consumer.class == Combi::Queue
174
+ service_result[:error].should eq "Timeout::Error"
168
175
  else
169
- service_result['error'].should eq error_message
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) { 'unknown action' }
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['error']['klass'].should eq error_message
189
- service_result['error']['message'].should eq 'do_other'
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
@@ -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 [{result: 'ok', response: service_response}.to_json]
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 [{result: 'ok', response: error_response}.to_json]
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
- if response_message[:response].respond_to? :succeed
42
- env['async.callback'].call [200, {}, DeferrableBody.new(response_message[:response])]
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.1.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-06-26 00:00:00.000000000 Z
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: '0'
196
+ version: 1.3.1
197
197
  requirements: []
198
198
  rubyforge_project:
199
199
  rubygems_version: 2.3.0