wamp_client 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -32
  3. data/lib/wamp/client/auth.rb +0 -27
  4. data/lib/wamp/client/check.rb +0 -27
  5. data/lib/wamp/client/connection.rb +40 -113
  6. data/lib/wamp/client/event.rb +78 -0
  7. data/lib/wamp/client/manager/base.rb +39 -0
  8. data/lib/wamp/client/manager/base_multiple.rb +37 -0
  9. data/lib/wamp/client/manager/establish.rb +168 -0
  10. data/lib/wamp/client/manager/registration.rb +183 -0
  11. data/lib/wamp/client/manager/require.rb +3 -0
  12. data/lib/wamp/client/manager/subscription.rb +55 -0
  13. data/lib/wamp/client/request/base.rb +125 -0
  14. data/lib/wamp/client/request/call.rb +111 -0
  15. data/lib/wamp/client/request/publish.rb +72 -0
  16. data/lib/wamp/client/request/register.rb +79 -0
  17. data/lib/wamp/client/request/require.rb +6 -0
  18. data/lib/wamp/client/request/subscribe.rb +78 -0
  19. data/lib/wamp/client/request/unregister.rb +71 -0
  20. data/lib/wamp/client/request/unsubscribe.rb +72 -0
  21. data/lib/wamp/client/response.rb +136 -0
  22. data/lib/wamp/client/serializer.rb +0 -29
  23. data/lib/wamp/client/session.rb +172 -839
  24. data/lib/wamp/client/transport/base.rb +4 -77
  25. data/lib/wamp/client/transport/event_machine_base.rb +0 -27
  26. data/lib/wamp/client/transport/faye_web_socket.rb +4 -31
  27. data/lib/wamp/client/transport/web_socket_event_machine.rb +3 -30
  28. data/lib/wamp/client/version.rb +1 -28
  29. data/lib/wamp/client.rb +1 -28
  30. data/spec/spec_helper.rb +3 -137
  31. data/spec/support/faye_web_socket_client_stub.rb +43 -0
  32. data/spec/support/test_transport.rb +50 -0
  33. data/spec/support/web_socket_event_machine_client_stub.rb +39 -0
  34. data/spec/wamp/client/connection_spec.rb +4 -4
  35. data/spec/wamp/client/session_spec.rb +135 -135
  36. data/spec/wamp/client/transport_spec.rb +2 -2
  37. data/wamp_client.gemspec +10 -9
  38. metadata +59 -38
  39. data/lib/wamp/client/defer.rb +0 -70
@@ -1,211 +1,52 @@
1
- =begin
2
-
3
- Copyright (c) 2018 Eric Chapman
4
-
5
- MIT License
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining
8
- a copy of this software and associated documentation files (the
9
- "Software"), to deal in the Software without restriction, including
10
- without limitation the rights to use, copy, modify, merge, publish,
11
- distribute, sublicense, and/or sell copies of the Software, and to
12
- permit persons to whom the Software is furnished to do so, subject to
13
- the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be
16
- included in all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
26
- =end
27
-
28
1
  require 'wamp/client/transport/base'
29
2
  require 'wamp/client/message'
30
3
  require 'wamp/client/check'
31
4
  require 'wamp/client/version'
5
+ require 'wamp/client/event'
6
+ require 'wamp/client/request/require'
7
+ require 'wamp/client/manager/require'
32
8
 
33
9
  module Wamp
34
10
  module Client
35
- WAMP_FEATURES = {
36
- caller: {
37
- features: {
38
- caller_identification: true,
39
- call_timeout: true,
40
- call_canceling: true,
41
- progressive_call_results: true
42
- }
43
- },
44
- callee: {
45
- features: {
46
- caller_identification: true,
47
- ##call_trustlevels: true,
48
- pattern_based_registration: true,
49
- shared_registration: true,
50
- ##call_timeout: true,
51
- call_canceling: true,
52
- progressive_call_results: true,
53
- registration_revocation: true
54
- }
55
- },
56
- publisher: {
57
- features: {
58
- publisher_identification: true,
59
- subscriber_blackwhite_listing: true,
60
- publisher_exclusion: true
61
- }
62
- },
63
- subscriber: {
64
- features: {
65
- publisher_identification: true,
66
- ##publication_trustlevels: true,
67
- pattern_based_subscription: true,
68
- subscription_revocation: true
69
- ##event_history: true,
70
- }
71
- }
11
+
12
+ CLOSED_SESSION_METHOD_LOOKUP = {
13
+ Message::Types::WELCOME => -> s, m { s.establish.welcome(m) },
14
+ Message::Types::CHALLENGE => -> s, m { s.establish.challenge(m) },
15
+ Message::Types::ABORT => -> s, m { s.establish.abort(m) },
72
16
  }
73
17
 
74
- HANDLER_LOOKUP = {
18
+ OPEN_SESSION_METHOD_LOOKUP = {
19
+ # Establish Response
20
+ Message::Types::GOODBYE => -> s, m { s.establish.goodbye(m) },
21
+
75
22
  # Error Responses
76
- Message::Types::SUBSCRIBE => -> s,m { s._process_SUBSCRIBE_error(m) },
77
- Message::Types::UNSUBSCRIBE => -> s,m { s._process_UNSUBSCRIBE_error(m) },
78
- Message::Types::PUBLISH => -> s,m { s._process_PUBLISH_error(m) },
79
- Message::Types::REGISTER => -> s,m { s._process_REGISTER_error(m) },
80
- Message::Types::UNREGISTER => -> s,m { s._process_UNREGISTER_error(m) },
81
- Message::Types::CALL => -> s,m { s._process_CALL_error(m) },
23
+ Message::Types::SUBSCRIBE => -> s, m { s.request[:subscribe].error(m) },
24
+ Message::Types::UNSUBSCRIBE => -> s, m { s.request[:unsubscribe].error(m) },
25
+ Message::Types::PUBLISH => -> s, m { s.request[:publish].error(m) },
26
+ Message::Types::REGISTER => -> s, m { s.request[:register].error(m) },
27
+ Message::Types::UNREGISTER => -> s, m { s.request[:unregister].error(m) },
28
+ Message::Types::CALL => -> s, m { s.request[:call].error(m) },
82
29
 
83
30
  # Result Responses
84
- Message::Types::SUBSCRIBED => -> s,m { s._process_SUBSCRIBED(m) },
85
- Message::Types::UNSUBSCRIBED => -> s,m { s._process_UNSUBSCRIBED(m) },
86
- Message::Types::PUBLISHED => -> s,m { s._process_PUBLISHED(m) },
87
- Message::Types::EVENT => -> s,m { s._process_EVENT(m) },
88
- Message::Types::REGISTERED => -> s,m { s._process_REGISTERED(m) },
89
- Message::Types::UNREGISTERED => -> s,m { s._process_UNREGISTERED(m) },
90
- Message::Types::INVOCATION => -> s,m { s._process_INVOCATION(m) },
91
- Message::Types::INTERRUPT => -> s,m { s._process_INTERRUPT(m) },
92
- Message::Types::RESULT => -> s,m { s._process_RESULT(m) },
31
+ Message::Types::SUBSCRIBED => -> s, m { s.request[:subscribe].success(m) },
32
+ Message::Types::UNSUBSCRIBED => -> s, m { s.request[:unsubscribe].success(m) },
33
+ Message::Types::PUBLISHED => -> s, m { s.request[:publish].success(m) },
34
+ Message::Types::EVENT => -> s, m { s.subscription.event(m) },
35
+ Message::Types::REGISTERED => -> s, m { s.request[:register].success(m) },
36
+ Message::Types::UNREGISTERED => -> s, m { s.request[:unregister].success(m) },
37
+ Message::Types::INVOCATION => -> s, m { s.registration.invoke(m) },
38
+ Message::Types::INTERRUPT => -> s, m { s.registration.interrupt(m) },
39
+ Message::Types::RESULT => -> s, m { s.request[:call].success(m) },
93
40
  }
94
41
 
95
- class CallResult
96
- attr_accessor :args, :kwargs
97
-
98
- def initialize(args=nil, kwargs=nil)
99
- self.args = args || []
100
- self.kwargs = kwargs || {}
101
- end
102
- end
103
-
104
- class CallError < Exception
105
- attr_accessor :error, :args, :kwargs
106
-
107
- def initialize(error, args=nil, kwargs=nil)
108
- self.error = error
109
- self.args = args || []
110
- self.kwargs = kwargs || {}
111
- end
112
- end
113
-
114
- class Subscription
115
- attr_accessor :topic, :handler, :options, :session, :id
116
-
117
- def initialize(topic, handler, options, session, id)
118
- self.topic = topic
119
- self.handler = handler
120
- self.options = options
121
- self.session = session
122
- self.id = id
123
- end
124
-
125
- def unsubscribe
126
- self.session.unsubscribe(self)
127
- end
128
-
129
- end
130
-
131
- class Registration
132
- attr_accessor :procedure, :handler, :i_handler, :options, :session, :id
133
-
134
- def initialize(procedure, handler, options, i_handler, session, id)
135
- self.procedure = procedure
136
- self.handler = handler
137
- self.options = options
138
- self.i_handler = i_handler
139
- self.session = session
140
- self.id = id
141
- end
142
-
143
- def unregister
144
- self.session.unregister(self)
145
- end
146
-
147
- end
148
-
149
- class Call
150
- attr_accessor :session, :id
151
-
152
- def initialize(session, id)
153
- self.session = session
154
- self.id = id
155
- end
156
-
157
- def cancel(mode='skip')
158
- self.session.cancel(self, mode)
159
- end
160
-
161
- end
162
-
163
42
  class Session
164
43
  include Check
44
+ include Event
165
45
 
166
- # on_join callback is called when the session joins the router. It has the following parameters
167
- # @param details [Hash] Object containing information about the joined session
168
- @on_join
169
- def on_join(&on_join)
170
- @on_join = on_join
171
- end
172
-
173
- # on_leave callback is called when the session leaves the router. It has the following attributes
174
- # @param reason [String] The reason the session left the router
175
- # @param details [Hash] Object containing information about the left session
176
- @on_leave
177
- def on_leave(&on_leave)
178
- @on_leave = on_leave
179
- end
180
-
181
- # on_challenge callback is called when an authentication challenge is received from the router. It has the
182
- # following attributes
183
- # @param authmethod [String] The type of auth being requested
184
- # @param extra [Hash] Hash containing additional information
185
- # @return signature, extras
186
- @on_challenge
187
- def on_challenge(&on_challenge)
188
- @on_challenge = on_challenge
189
- end
190
-
191
- # Simple setter for callbacks
192
- def on(event, &callback)
193
- case event
194
- when :join
195
- self.on_join(&callback)
196
- when :challenge
197
- self.on_challenge(&callback)
198
- when :leave
199
- self.on_leave(&callback)
200
- else
201
- raise RuntimeError, "Unknown on(event) '#{event}'"
202
- end
203
- end
46
+ attr_accessor :transport, :options, :request, :callback,
47
+ :subscription, :registration, :establish
204
48
 
205
- attr_accessor :id, :realm, :transport, :options
206
-
207
- # Private attributes
208
- attr_accessor :_goodbye_sent, :_requests, :_subscriptions, :_registrations, :_defers
49
+ create_event [:join, :challenge, :leave]
209
50
 
210
51
  # Constructor
211
52
  # @param transport [Transport::Base] The transport that the session will use
@@ -215,697 +56,187 @@ module Wamp
215
56
  def initialize(transport, options={})
216
57
 
217
58
  # Parameters
218
- self.id = nil
219
- self.realm = nil
220
59
  self.options = options || {}
221
60
 
61
+ # Log the event
62
+ logger.info("#{self.class.name} created with options")
63
+ logger.info(" uri: #{options[:uri]}")
64
+ logger.info(" realm: #{options[:realm]}")
65
+
66
+ # Create the send message lambda for the request objects
67
+ send_message_lambda = -> m { send_message(m) }
68
+
222
69
  # Outstanding Requests
223
- self._requests = {
224
- publish: {},
225
- subscribe: {},
226
- unsubscribe: {},
227
- call: {},
228
- register: {},
229
- unregister: {}
70
+ self.request = {
71
+ publish: Request::Publish.new(self, send_message_lambda),
72
+ subscribe: Request::Subscribe.new(self, send_message_lambda) { |s_id, s| self.subscription.add(s_id, s) },
73
+ unsubscribe: Request::Unsubscribe.new(self, send_message_lambda) { |s_id| self.subscription.remove(s_id) },
74
+ call: Request::Call.new(self, send_message_lambda),
75
+ register: Request::Register.new(self, send_message_lambda) { |r_id, r| self.registration.add(r_id, r) },
76
+ unregister: Request::Unregister.new(self, send_message_lambda) { |r_id| self.registration.remove(r_id) },
230
77
  }
231
78
 
232
79
  # Init Subs and Regs in place
233
- self._subscriptions = {}
234
- self._registrations = {}
235
- self._defers = {}
80
+ self.subscription = Manager::Subscription.new(self, send_message_lambda)
81
+ self.registration = Manager::Registration.new(self, send_message_lambda)
82
+ self.establish = Manager::Establish.new(self, send_message_lambda)
83
+
84
+ # Setup session callbacks
85
+ self.callback = {}
236
86
 
237
87
  # Setup Transport
238
88
  self.transport = transport
239
- self.transport.on_message do |msg|
240
- self._receive_message(msg)
89
+ self.transport.on :message do |msg|
90
+ receive_message(msg)
241
91
  end
242
92
 
243
- # Other parameters
244
- self._goodbye_sent = false
245
-
246
- # Setup session callbacks
247
- @on_join = nil
248
- @on_leave = nil
249
- @on_challenge = nil
250
-
251
93
  end
252
94
 
253
95
  # Returns 'true' if the session is open
96
+ #
254
97
  def is_open?
255
- !self.id.nil?
98
+ self.establish.is_open?
99
+ end
100
+
101
+ # Returns the ID of the session
102
+ #
103
+ def id
104
+ self.establish.id
105
+ end
106
+
107
+ # Returns the realm of the session
108
+ #
109
+ def realm
110
+ self.establish.realm
256
111
  end
257
112
 
258
113
  # Joins the WAMP Router
114
+ #
259
115
  # @param realm [String] The name of the realm
260
116
  def join(realm)
261
- if is_open?
262
- raise RuntimeError, "Session must be closed to call 'join'"
263
- end
117
+ check_closed
264
118
 
119
+ # Check params
265
120
  self.class.check_uri('realm', realm)
266
121
 
267
- self.realm = realm
268
-
269
- details = {}
270
- details[:roles] = WAMP_FEATURES
271
- details[:agent] = "Ruby-Wamp::Client-#{VERSION}"
272
- details[:authid] = self.options[:authid] if self.options[:authid]
273
- details[:authmethods] = self.options[:authmethods] if self.options[:authmethods]
274
-
275
- # Send Hello message
276
- hello = Message::Hello.new(realm, details)
277
- self._send_message(hello)
122
+ # Attempt to join
123
+ self.establish.join(realm)
278
124
  end
279
125
 
280
126
  # Leaves the WAMP Router
127
+ #
281
128
  # @param reason [String] URI signalling the reason for leaving
282
129
  def leave(reason='wamp.close.normal', message='user initiated')
283
- unless is_open?
284
- raise RuntimeError, "Session must be opened to call 'leave'"
285
- end
130
+ check_open
286
131
 
132
+ # Check params
287
133
  self.class.check_uri('reason', reason, true)
288
134
  self.class.check_string('message', message, true)
289
135
 
290
- details = {}
291
- details[:message] = message
292
-
293
- # Send Goodbye message
294
- goodbye = Message::Goodbye.new(details, reason)
295
- self._send_message(goodbye)
296
- self._goodbye_sent = true
297
- end
298
-
299
- # Generates an ID according to the specification (Section 5.1.2)
300
- def _generate_id
301
- rand(0..9007199254740992)
136
+ # Leave the session
137
+ self.establish.leave(reason, message)
302
138
  end
303
139
 
304
- # Converts and error message to a hash
305
- # @param msg [Message::Error]
306
- def _error_to_hash(msg)
307
- {
308
- error: msg.error,
309
- args: msg.arguments,
310
- kwargs: msg.argumentskw
311
- }
312
- end
313
-
314
- # Sends a message
315
- # @param msg [Message::Base]
316
- def _send_message(msg)
317
- # Log the message
318
- logger.debug("#{self.class.name} TX: #{msg.to_s}")
319
-
320
- # Send it to the transport
321
- self.transport.send_message(msg.payload)
322
- end
323
-
324
- # Processes received messages
325
- # @param msg [Array]
326
- def _receive_message(msg)
327
-
328
- # Print the raw message
329
- logger.debug("#{self.class.name} RX(raw): #{msg.to_s}")
330
-
331
- # Parse the WAMP message
332
- message = Message.parse(msg)
333
-
334
- # Print the parsed WAMP message
335
- logger.debug("#{self.class.name} RX: #{message.to_s}")
336
-
337
- # WAMP Session is not open
338
- if self.id.nil?
339
-
340
- # Parse the welcome message
341
- if message.is_a? Message::Welcome
342
- # Get the session ID
343
- self.id = message.session
344
-
345
- # Log joining the session
346
- logger.info("#{self.class.name} joined session with realm '#{message.details[:realm]}'")
347
-
348
- # Call the callback if it is set
349
- @on_join.call(message.details) unless @on_join.nil?
350
- elsif message.is_a? Message::Challenge
351
- # Log challenge received
352
- logger.debug("#{self.class.name} auth challenge '#{message.authmethod}', extra: #{message.extra}")
353
-
354
- # Call the callback if set
355
- if @on_challenge
356
- signature, extra = @on_challenge.call(message.authmethod, message.extra)
357
- else
358
- signature = nil
359
- extra = nil
360
- end
361
-
362
- signature ||= ''
363
- extra ||= {}
364
-
365
- authenticate = Message::Authenticate.new(signature, extra)
366
- self._send_message(authenticate)
367
-
368
- elsif message.is_a? Message::Abort
369
- # Log leaving the session
370
- logger.info("#{self.class.name} left session '#{message.reason}'")
371
-
372
- # Call the callback if it is set
373
- @on_leave.call(message.reason, message.details) unless @on_leave.nil?
374
- end
375
-
376
- # Wamp Session is open
377
- else
378
-
379
- # If goodbye, close the session
380
- if message.is_a? Message::Goodbye
381
-
382
- # If we didn't send the goodbye, respond
383
- unless self._goodbye_sent
384
- goodbye = Message::Goodbye.new({}, 'wamp.error.goodbye_and_out')
385
- self._send_message(goodbye)
386
- end
387
-
388
- # Close out session
389
- self.id = nil
390
- self.realm = nil
391
- self._goodbye_sent = false
392
- @on_leave.call(message.reason, message.details) unless @on_leave.nil?
393
-
394
- else
395
-
396
- # Else this is a normal message. Lookup the handler and call it
397
- type = message.is_a?(Message::Error) ? message.request_type : message.class.type
398
- handler = HANDLER_LOOKUP[type]
399
-
400
- if handler != nil
401
- handler.call(self, message)
402
- else
403
- logger.error("#{self.class.name} unknown message type '#{type}'")
404
- end
405
- end
406
- end
407
-
408
- end
409
-
410
- #region Subscribe Logic
411
-
412
140
  # Subscribes to a topic
141
+ #
413
142
  # @param topic [String] The topic to subscribe to
414
143
  # @param handler [lambda] The handler(args, kwargs, details) when an event is received
415
144
  # @param options [Hash] The options for the subscription
416
145
  # @param callback [block] The callback(subscription, error) called to signal if the subscription was a success or not
417
146
  def subscribe(topic, handler, options={}, &callback)
418
- unless is_open?
419
- raise RuntimeError, "Session must be open to call 'subscribe'"
420
- end
147
+ check_open
421
148
 
149
+ # Check params
422
150
  self.class.check_uri('topic', topic)
423
151
  self.class.check_dict('options', options)
424
152
  self.class.check_nil('handler', handler, false)
425
153
 
426
- # Create a new subscribe request
427
- request = self._generate_id
428
- self._requests[:subscribe][request] = {t: topic, h: handler, o: options, c: callback}
429
-
430
- # Send the message
431
- subscribe = Message::Subscribe.new(request, options, topic)
432
- self._send_message(subscribe)
154
+ # Make the request
155
+ make_request(:subscribe, :request, topic, handler, options, &callback)
433
156
  end
434
157
 
435
- # Processes the response to a subscribe request
436
- # @param msg [Message::Subscribed] The response from the subscribe
437
- def _process_SUBSCRIBED(msg)
438
-
439
- # Remove the pending subscription, add it to the registered ones, and inform the caller
440
- s = self._requests[:subscribe].delete(msg.subscribe_request)
441
- if s
442
-
443
- details = {}
444
- details[:topic] = s[:t] unless details[:topic]
445
- details[:type] = 'subscribe'
446
- details[:session] = self
447
-
448
- n_s = Subscription.new(s[:t], s[:h], s[:o], self, msg.subscription)
449
- self._subscriptions[msg.subscription] = n_s
450
- c = s[:c]
451
- c.call(n_s, nil, details) if c
452
- end
453
-
454
- end
455
-
456
- # Processes an error from a request
457
- # @param msg [Message::Error] The response from the subscribe
458
- def _process_SUBSCRIBE_error(msg)
459
-
460
- # Remove the pending subscription and inform the caller of the failure
461
- s = self._requests[:subscribe].delete(msg.request_request)
462
- if s
463
-
464
- details = msg.details || {}
465
- details[:topic] = s[:t] unless details[:topic]
466
- details[:type] = 'subscribe'
467
- details[:session] = self
468
-
469
- c = s[:c]
470
- c.call(nil, self._error_to_hash(msg), details) if c
471
- end
472
-
473
- end
474
-
475
- # Processes and event from the broker
476
- # @param msg [Message::Event] An event that was published
477
- def _process_EVENT(msg)
478
-
479
- args = msg.publish_arguments || []
480
- kwargs = msg.publish_argumentskw || {}
481
-
482
- s = self._subscriptions[msg.subscribed_subscription]
483
- if s
484
- details = msg.details || {}
485
- details[:publication] = msg.published_publication
486
- details[:session] = self
487
-
488
- h = s.handler
489
- h.call(args, kwargs, details) if h
490
- end
491
-
492
- end
493
-
494
- #endregion
495
-
496
- #region Unsubscribe Logic
497
-
498
158
  # Unsubscribes from a subscription
159
+ #
499
160
  # @param subscription [Subscription] The subscription object from when the subscription was created
500
161
  # @param callback [block] The callback(subscription, error, details) called to signal if the subscription was a success or not
501
162
  def unsubscribe(subscription, &callback)
502
- unless is_open?
503
- raise RuntimeError, "Session must be open to call 'unsubscribe'"
504
- end
163
+ check_open
505
164
 
165
+ # Check params
506
166
  self.class.check_nil('subscription', subscription, false)
507
167
 
508
- # Create a new unsubscribe request
509
- request = self._generate_id
510
- self._requests[:unsubscribe][request] = { s: subscription, c: callback }
511
-
512
- # Send the message
513
- unsubscribe = Message::Unsubscribe.new(request, subscription.id)
514
- self._send_message(unsubscribe)
515
- end
516
-
517
- # Processes the response to a unsubscribe request
518
- # @param msg [Message::Unsubscribed] The response from the unsubscribe
519
- def _process_UNSUBSCRIBED(msg)
520
-
521
- # Remove the pending unsubscription, add it to the registered ones, and inform the caller
522
- s = self._requests[:unsubscribe].delete(msg.unsubscribe_request)
523
- if s
524
- n_s = s[:s]
525
- self._subscriptions.delete(n_s.id)
526
-
527
- details = {}
528
- details[:topic] = s[:s].topic
529
- details[:type] = 'unsubscribe'
530
- details[:session] = self
531
-
532
- c = s[:c]
533
- c.call(n_s, nil, details) if c
534
- end
535
-
168
+ # Make the request
169
+ make_request(:unsubscribe, :request, subscription, &callback)
536
170
  end
537
171
 
538
-
539
- # Processes an error from a request
540
- # @param msg [Message::Error] The response from the subscribe
541
- def _process_UNSUBSCRIBE_error(msg)
542
-
543
- # Remove the pending subscription and inform the caller of the failure
544
- s = self._requests[:unsubscribe].delete(msg.request_request)
545
- if s
546
-
547
- details = msg.details || {}
548
- details[:topic] = s[:s].topic unless details[:topic]
549
- details[:type] = 'unsubscribe'
550
- details[:session] = self
551
-
552
- c = s[:c]
553
- c.call(nil, self._error_to_hash(msg), details) if c
554
- end
555
-
556
- end
557
-
558
- #endregion
559
-
560
- #region Publish Logic
561
-
562
172
  # Publishes and event to a topic
173
+ #
563
174
  # @param topic [String] The topic to publish the event to
564
175
  # @param args [Array] The arguments
565
176
  # @param kwargs [Hash] The keyword arguments
566
177
  # @param options [Hash] The options for the publish
567
178
  # @param callback [block] The callback(publish, error, details) called to signal if the publish was a success or not
568
179
  def publish(topic, args=nil, kwargs=nil, options={}, &callback)
569
- unless is_open?
570
- raise RuntimeError, "Session must be open to call 'publish'"
571
- end
180
+ check_open
572
181
 
182
+ # Check params
573
183
  self.class.check_uri('topic', topic)
574
184
  self.class.check_dict('options', options)
575
185
  self.class.check_list('args', args, true)
576
186
  self.class.check_dict('kwargs', kwargs, true)
577
187
 
578
- # Create a new publish request
579
- request = self._generate_id
580
- self._requests[:publish][request] = {t: topic, a: args, k: kwargs, o: options, c: callback} if options[:acknowledge]
581
-
582
- # Send the message
583
- publish = Message::Publish.new(request, options, topic, args, kwargs)
584
- self._send_message(publish)
585
- end
586
-
587
- # Processes the response to a publish request
588
- # @param msg [Message::Published] The response from the subscribe
589
- def _process_PUBLISHED(msg)
590
-
591
- # Remove the pending publish and alert the callback
592
- p = self._requests[:publish].delete(msg.publish_request)
593
- if p
594
-
595
- details = {}
596
- details[:topic] = p[:t]
597
- details[:type] = 'publish'
598
- details[:publication] = msg.publication
599
- details[:session] = self
600
-
601
- c = p[:c]
602
- c.call(p, nil, details) if c
603
- end
604
-
188
+ # Make the request
189
+ make_request(:publish, :request, topic, args, kwargs, options, &callback)
605
190
  end
606
191
 
607
- # Processes an error from a publish request
608
- # @param msg [Message::Error] The response from the subscribe
609
- def _process_PUBLISH_error(msg)
610
-
611
- # Remove the pending publish and inform the caller of the failure
612
- s = self._requests[:publish].delete(msg.request_request)
613
- if s
614
-
615
- details = msg.details || {}
616
- details[:topic] = s[:t] unless details[:topic]
617
- details[:type] = 'publish'
618
- details[:session] = self
619
-
620
- c = s[:c]
621
- c.call(nil, self._error_to_hash(msg), details) if c
622
- end
623
-
624
- end
625
-
626
- #endregion
627
-
628
- #region Register Logic
629
-
630
192
  # Register to a procedure
193
+ #
631
194
  # @param procedure [String] The procedure to register for
632
195
  # @param handler [lambda] The handler(args, kwargs, details) when an invocation is received
633
196
  # @param options [Hash, nil] The options for the registration
634
197
  # @param interrupt [lambda] The handler(request, mode) when an interrupt is received
635
198
  # @param callback [block] The callback(registration, error, details) called to signal if the registration was a success or not
636
199
  def register(procedure, handler, options=nil, interrupt=nil, &callback)
637
- unless is_open?
638
- raise RuntimeError, "Session must be open to call 'register'"
639
- end
200
+ check_open
640
201
 
641
202
  options ||= {}
642
203
 
204
+ # Check params
643
205
  self.class.check_uri('procedure', procedure)
644
206
  self.class.check_nil('handler', handler, false)
645
207
 
646
- # Create a new registration request
647
- request = self._generate_id
648
- self._requests[:register][request] = {p: procedure, h: handler, i: interrupt, o: options, c: callback}
649
-
650
- # Send the message
651
- register = Message::Register.new(request, options, procedure)
652
- self._send_message(register)
653
- end
654
-
655
- # Processes the response to a register request
656
- # @param msg [Message::Registered] The response from the subscribe
657
- def _process_REGISTERED(msg)
658
-
659
- # Remove the pending subscription, add it to the registered ones, and inform the caller
660
- r = self._requests[:register].delete(msg.register_request)
661
- if r
662
- n_r = Registration.new(r[:p], r[:h], r[:o], r[:i], self, msg.registration)
663
- self._registrations[msg.registration] = n_r
664
-
665
- details = {}
666
- details[:procedure] = r[:p]
667
- details[:type] = 'register'
668
- details[:session] = self
669
-
670
- c = r[:c]
671
- c.call(n_r, nil, details) if c
672
- end
673
-
674
- end
675
-
676
- # Processes an error from a request
677
- # @param msg [Message::Error] The response from the register
678
- def _process_REGISTER_error(msg)
679
-
680
- # Remove the pending registration and inform the caller of the failure
681
- r = self._requests[:register].delete(msg.request_request)
682
- if r
683
-
684
- details = msg.details || {}
685
- details[:procedure] = r[:p] unless details[:procedure]
686
- details[:type] = 'register'
687
- details[:session] = self
688
-
689
- c = r[:c]
690
- c.call(nil, self._error_to_hash(msg), details) if c
691
- end
692
-
693
- end
694
-
695
- # Sends an error back to the caller
696
- # @param request[Integer] - The request ID
697
- # @param error
698
- def _send_INVOCATION_error(request, error, check_defer=false)
699
- # Prevent responses for defers that have already completed or had an error
700
- if check_defer and not self._defers[request]
701
- return
702
- end
703
-
704
- if error.nil?
705
- error = CallError.new('wamp.error.runtime')
706
- elsif not error.is_a?(CallError)
707
- backtrace = error.is_a?(Exception) ? error.backtrace : nil
708
- error = CallError.new('wamp.error.runtime', [error.to_s], { backtrace: backtrace })
709
- end
710
-
711
- error_msg = Message::Error.new(Message::Types::INVOCATION, request, {}, error.error, error.args, error.kwargs)
712
- self._send_message(error_msg)
208
+ # Make the request
209
+ make_request(:register, :request, procedure, handler, options, interrupt, &callback)
713
210
  end
714
211
 
715
212
  # Sends a result for the invocation
213
+ #
716
214
  # @param request [Integer] - The id of the request
717
215
  # @param result [CallError, CallResult, anything] - If it is a CallError, the error will be returned
718
216
  # @param options [Hash] - The options to be sent with the yield
719
217
  def yield(request, result, options={}, check_defer=false)
720
- # Prevent responses for defers that have already completed or had an error
721
- if check_defer and not self._defers[request]
722
- return
723
- end
724
-
725
- # Wrap the result accordingly
726
- if result.nil?
727
- result = CallResult.new
728
- elsif result.is_a?(CallError)
729
- # Do nothing
730
- elsif not result.is_a?(CallResult)
731
- result = CallResult.new([result])
732
- end
733
-
734
- # Send either the error or the response
735
- if result.is_a?(CallError)
736
- self._send_INVOCATION_error(request, result)
737
- else
738
- yield_msg = Message::Yield.new(request, options, result.args, result.kwargs)
739
- self._send_message(yield_msg)
740
- end
218
+ check_open
741
219
 
742
- # Remove the defer if this was not a progress update
743
- if check_defer and options[:progress] == nil
744
- self._defers.delete(request)
745
- end
220
+ # Call the registration yield method
221
+ self.registration.yield(request, result, options, check_defer)
746
222
  end
747
223
 
748
-
749
- # Processes and event from the broker
750
- # @param msg [Message::Invocation] An procedure that was called
751
- def _process_INVOCATION(msg)
752
-
753
- request = msg.request
754
- args = msg.call_arguments || []
755
- kwargs = msg.call_argumentskw || {}
756
-
757
- details = msg.details || {}
758
- details[:request] = request
759
- details[:session] = self
760
-
761
- r = self._registrations[msg.registered_registration]
762
- if r
763
- h = r.handler
764
- if h
765
- begin
766
- value = h.call(args, kwargs, details)
767
-
768
- # If a defer was returned, handle accordingly
769
- if value.is_a? Defer::CallDefer
770
- value.request = request
771
- value.registration = msg.registered_registration
772
-
773
- # Store the defer
774
- self._defers[request] = value
775
-
776
- # On complete, send the result
777
- value.on_complete do |defer, result|
778
- self.yield(defer.request, result, {}, true)
779
- end
780
-
781
- # On error, send the error
782
- value.on_error do |defer, error|
783
- error = CallError.new("wamp.error.runtime", [error]) if error.is_a?(String)
784
- self.yield(defer.request, error, {}, true)
785
- end
786
-
787
- # For progressive, return the progress
788
- if value.is_a? Defer::ProgressiveCallDefer
789
- value.on_progress do |defer, result|
790
- self.yield(defer.request, result, {progress: true}, true)
791
- end
792
- end
793
-
794
- # Else it was a normal response
795
- else
796
- self.yield(request, value)
797
- end
798
-
799
- rescue Exception => error
800
- self._send_INVOCATION_error(request, error)
801
- end
802
-
803
- end
804
- end
805
- end
806
-
807
- # Processes the interrupt
808
- # @param msg [Message::Interrupt] An interrupt to a procedure
809
- def _process_INTERRUPT(msg)
810
-
811
- request = msg.invocation_request
812
- mode = msg.options[:mode]
813
-
814
- defer = self._defers[request]
815
- if defer
816
- r = self._registrations[defer.registration]
817
- if r
818
- # If it exists, call the interrupt handler to inform it of the interrupt
819
- i = r.i_handler
820
- error = nil
821
- if i
822
- begin
823
- error = i.call(request, mode)
824
- rescue Exception => e
825
- error = e
826
- end
827
- end
828
-
829
- error ||= 'interrupt'
830
-
831
- # Send the error back to the client
832
- self._send_INVOCATION_error(request, error, true)
833
- end
834
-
835
- # Delete the defer
836
- self._defers.delete(request)
837
- end
838
-
839
- end
840
-
841
- #endregion
842
-
843
- #region Unregister Logic
844
-
845
224
  # Unregisters from a procedure
225
+ #
846
226
  # @param registration [Registration] The registration object from when the registration was created
847
227
  # @param callback [block] The callback(registration, error, details) called to signal if the unregistration was a success or not
848
228
  def unregister(registration, &callback)
849
- unless is_open?
850
- raise RuntimeError, "Session must be open to call 'unregister'"
851
- end
229
+ check_open
852
230
 
231
+ # Check params
853
232
  self.class.check_nil('registration', registration, false)
854
233
 
855
- # Create a new unsubscribe request
856
- request = self._generate_id
857
- self._requests[:unregister][request] = { r: registration, c: callback }
858
-
859
- # Send the message
860
- unregister = Message::Unregister.new(request, registration.id)
861
- self._send_message(unregister)
234
+ # Make the request
235
+ make_request(:unregister, :request, registration, &callback)
862
236
  end
863
237
 
864
- # Processes the response to a unregister request
865
- # @param msg [Message::Unregistered] The response from the unsubscribe
866
- def _process_UNREGISTERED(msg)
867
-
868
- # Remove the pending unregistration, add it to the registered ones, and inform the caller
869
- r = self._requests[:unregister].delete(msg.unregister_request)
870
- if r
871
- r_s = r[:r]
872
- self._registrations.delete(r_s.id)
873
-
874
- details = {}
875
- details[:procedure] = r_s.procedure
876
- details[:type] = 'unregister'
877
- details[:session] = self
878
-
879
- c = r[:c]
880
- c.call(r_s, nil, details) if c
881
- end
882
-
883
- end
884
-
885
- # Processes an error from a request
886
- # @param msg [Message::Error] The response from the subscribe
887
- def _process_UNREGISTER_error(msg)
888
-
889
- # Remove the pending subscription and inform the caller of the failure
890
- r = self._requests[:unregister].delete(msg.request_request)
891
- if r
892
-
893
- details = msg.details || {}
894
- details[:procedure] = r[:r].procedure unless details[:procedure]
895
- details[:type] = 'unregister'
896
- details[:session] = self
897
-
898
- c = r[:c]
899
- c.call(nil, self._error_to_hash(msg), details) if c
900
- end
901
-
902
- end
903
-
904
- #endregion
905
-
906
- #region Call Logic
907
-
908
238
  # Publishes and event to a topic
239
+ #
909
240
  # @param procedure [String] The procedure to invoke
910
241
  # @param args [Array] The arguments
911
242
  # @param kwargs [Hash] The keyword arguments
@@ -913,106 +244,108 @@ module Wamp
913
244
  # @param callback [block] The callback(result, error, details) called to signal if the call was a success or not
914
245
  # @return [Call] An object representing the call
915
246
  def call(procedure, args=nil, kwargs=nil, options={}, &callback)
916
- unless is_open?
917
- raise RuntimeError, "Session must be open to call 'call'"
918
- end
247
+ check_open
919
248
 
249
+ # Check params
920
250
  self.class.check_uri('procedure', procedure)
921
251
  self.class.check_dict('options', options)
922
252
  self.class.check_list('args', args, true)
923
253
  self.class.check_dict('kwargs', kwargs, true)
924
254
 
925
- # Create a new call request
926
- request = self._generate_id
927
- self._requests[:call][request] = {p: procedure, a: args, k: kwargs, o: options, c: callback}
928
-
929
- # Send the message
930
- msg = Message::Call.new(request, options, procedure, args, kwargs)
931
- self._send_message(msg)
255
+ # Make the request
256
+ request_id = make_request(:call, :request, procedure, args, kwargs, options, &callback)
932
257
 
933
- call = Call.new(self, request)
258
+ # Create the call object
259
+ call = Request::CallObject.new(self, request_id)
934
260
 
935
261
  # Timeout Logic
936
262
  if options[:timeout] and options[:timeout] > 0
263
+ # Once the timer expires, if the call hasn't completed, cancel it
937
264
  self.transport.add_timer(options[:timeout]) do
938
- # Once the timer expires, if the call hasn't completed, cancel it
939
- if self._requests[:call][call.id]
940
- call.cancel
941
- end
265
+ call.cancel
942
266
  end
943
267
  end
944
268
 
945
269
  call
946
270
  end
947
271
 
948
- # Processes the response to a publish request
949
- # @param msg [Message::Result] The response from the call
950
- def _process_RESULT(msg)
951
-
952
- details = msg.details || {}
272
+ # Cancels a call
273
+ #
274
+ # @param call [Call] - The call object
275
+ # @param mode [String] - The mode of the skip. Options are 'skip', 'kill', 'killnowait'
276
+ def cancel(call, mode='skip')
277
+ check_open
953
278
 
954
- call = self._requests[:call][msg.call_request]
279
+ # Check params
280
+ self.class.check_nil('call', call, false)
955
281
 
956
- # Don't remove if progress is true and the options had receive_progress true
957
- self._requests[:call].delete(msg.call_request) unless (details[:progress] and (call and call[:o][:receive_progress]))
282
+ # Cancel the request
283
+ make_request(:call, :cancel, call.id, mode)
284
+ end
958
285
 
959
- if call
960
- details[:procedure] = call[:p] unless details[:procedure]
961
- details[:type] = 'call'
962
- details[:session] = self
286
+ private
963
287
 
964
- c = call[:c]
965
- c.call(CallResult.new(msg.yield_arguments, msg.yield_argumentskw), nil, details) if c
288
+ def check_closed
289
+ if is_open?
290
+ raise RuntimeError, "session must be closed to call this method"
966
291
  end
292
+ end
967
293
 
294
+ def check_open
295
+ unless is_open?
296
+ raise RuntimeError, "session must be open to call this method"
297
+ end
968
298
  end
969
299
 
970
- # Processes an error from a call request
971
- # @param msg [Message::Error] The response from the call
972
- def _process_CALL_error(msg)
300
+ def make_request(name, method, *args, &callback)
301
+ self.request[name].send(method, *args, &callback)
302
+ end
973
303
 
974
- # Remove the pending publish and inform the caller of the failure
975
- call = self._requests[:call].delete(msg.request_request)
976
- if call
304
+ def logger
305
+ Wamp::Client.logger
306
+ end
977
307
 
978
- details = msg.details || {}
979
- details[:procedure] = call[:p] unless details[:procedure]
980
- details[:type] = 'call'
981
- details[:session] = self
308
+ def send_message(msg)
982
309
 
983
- c = call[:c]
984
- c.call(nil, self._error_to_hash(msg), details) if c
985
- end
310
+ # Log the message
311
+ logger.debug("#{self.class.name} TX: #{msg.to_s}")
986
312
 
313
+ # Send it to the transport
314
+ self.transport.send_message(msg.payload)
987
315
  end
988
316
 
989
- #endregion
317
+ def receive_message(msg)
990
318
 
991
- #region Cancel Logic
319
+ # Print the raw message
320
+ logger.debug("#{self.class.name} RX(raw): #{msg.to_s}")
992
321
 
993
- # Cancels a call
994
- # @param call [Call] - The call object
995
- # @param mode [String] - The mode of the skip. Options are 'skip', 'kill', 'killnowait'
996
- def cancel(call, mode='skip')
997
- unless is_open?
998
- raise RuntimeError, "Session must be open to call 'cancel'"
999
- end
322
+ # Parse the WAMP message
323
+ message = Message.parse(msg)
1000
324
 
1001
- self.class.check_nil('call', call, false)
325
+ # Print the parsed WAMP message
326
+ logger.debug("#{self.class.name} RX: #{message.to_s}")
1002
327
 
1003
- # Send the message
1004
- cancel = Message::Cancel.new(call.id, { mode: mode })
1005
- self._send_message(cancel)
1006
- end
328
+ # Get the lookup based on the state of the session
329
+ lookup = self.is_open? ? OPEN_SESSION_METHOD_LOOKUP : CLOSED_SESSION_METHOD_LOOKUP
1007
330
 
1008
- #endregion
331
+ # Get the type of message
332
+ type = message.is_a?(Message::Error) ? message.request_type : message.class.type
1009
333
 
1010
- private
334
+ # Get the handler
335
+ handler = lookup[type]
1011
336
 
1012
- # Returns the logger
1013
- #
1014
- def logger
1015
- Wamp::Client.logger
337
+ # Execute the handler
338
+ if handler != nil
339
+ # Catch any standard exception and log it
340
+ begin
341
+ handler.call(self, message)
342
+ rescue StandardError => e
343
+ logger.error("#{self.class.name} - #{e.message}")
344
+ e.backtrace.each { |line| logger.error(" #{line}") }
345
+ end
346
+ else
347
+ logger.error("#{self.class.name} unknown message type '#{type}'")
348
+ end
1016
349
  end
1017
350
 
1018
351
  end