wamp_client 0.1.4 → 0.2.0

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.
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