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.
- checksums.yaml +4 -4
- data/README.md +28 -26
- data/lib/{wamp_client.rb → wamp/client.rb} +8 -8
- data/lib/{wamp_client → wamp/client}/auth.rb +13 -11
- data/lib/wamp/client/check.rb +86 -0
- data/lib/wamp/client/connection.rb +249 -0
- data/lib/{wamp_client → wamp/client}/defer.rb +29 -27
- data/lib/wamp/client/message.rb +1322 -0
- data/lib/{wamp_client → wamp/client}/serializer.rb +26 -24
- data/lib/wamp/client/session.rb +1001 -0
- data/lib/wamp/client/transport/base.rb +152 -0
- data/lib/{wamp_client → wamp/client}/transport/event_machine_base.rb +19 -17
- data/lib/wamp/client/transport/faye_web_socket.rb +85 -0
- data/lib/wamp/client/transport/web_socket_event_machine.rb +88 -0
- data/lib/{wamp_client → wamp/client}/version.rb +5 -3
- data/scripts/gen_message.rb +54 -53
- data/spec/spec_helper.rb +3 -3
- data/spec/{auth_spec.rb → wamp/client/auth_spec.rb} +2 -2
- data/spec/{check_spec.rb → wamp/client/check_spec.rb} +2 -2
- data/spec/{connection_spec.rb → wamp/client/connection_spec.rb} +7 -7
- data/spec/{message_spec.rb → wamp/client/message_spec.rb} +298 -298
- data/spec/{session_spec.rb → wamp/client/session_spec.rb} +134 -134
- data/spec/{transport_spec.rb → wamp/client/transport_spec.rb} +4 -4
- data/wamp_client.gemspec +2 -2
- metadata +50 -50
- data/lib/wamp_client/check.rb +0 -84
- data/lib/wamp_client/connection.rb +0 -247
- data/lib/wamp_client/message.rb +0 -1348
- data/lib/wamp_client/session.rb +0 -1000
- data/lib/wamp_client/transport/base.rb +0 -151
- data/lib/wamp_client/transport/faye_web_socket.rb +0 -83
- data/lib/wamp_client/transport/web_socket_event_machine.rb +0 -86
@@ -1,6 +1,6 @@
|
|
1
1
|
=begin
|
2
2
|
|
3
|
-
Copyright (c)
|
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
|
31
|
-
module
|
32
|
-
|
30
|
+
module Wamp
|
31
|
+
module Client
|
32
|
+
module Serializer
|
33
|
+
class Base
|
33
34
|
|
34
|
-
|
35
|
+
attr_accessor :type
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
# Serializes the object
|
38
|
+
# @param object - The object to serialize
|
39
|
+
def serialize(object)
|
39
40
|
|
40
|
-
|
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
|
-
|
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
|
-
|
52
|
+
class JSONSerializer < Base
|
50
53
|
|
51
|
-
|
54
|
+
def initialize
|
55
|
+
self.type = 'json'
|
56
|
+
end
|
52
57
|
|
53
|
-
|
54
|
-
|
55
|
-
|
58
|
+
def serialize(object)
|
59
|
+
JSON.generate object
|
60
|
+
end
|
56
61
|
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|