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