wamp 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -1
- data/Gemfile.lock +7 -1
- data/VERSION +1 -1
- data/demo/client.html +8 -3
- data/demo/client.js +24 -24
- data/demo/client.rb +29 -0
- data/lib/wamp.rb +11 -0
- data/lib/wamp/bindable.rb +16 -0
- data/lib/wamp/client.rb +118 -0
- data/lib/wamp/engines/memory.rb +104 -0
- data/lib/wamp/protocols/version_1.rb +128 -0
- data/lib/wamp/server.rb +57 -80
- data/lib/wamp/socket.rb +3 -4
- data/lib/wamp/topic.rb +32 -3
- data/spec/lib/wamp/client_spec.rb +9 -0
- data/spec/lib/wamp/engines/memory_spec.rb +118 -0
- data/spec/lib/wamp/protocols/version_1_spec.rb +80 -0
- data/spec/lib/wamp/server_spec.rb +5 -6
- data/spec/lib/wamp/socket_spec.rb +6 -6
- data/spec/lib/wamp/topic_spec.rb +1 -1
- data/spec/support/dummy_socket.rb +11 -0
- data/wamp.gemspec +20 -5
- metadata +49 -8
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module WAMP::Protocols
|
4
|
+
|
5
|
+
# Describes the WAMP protocol messages per http://www.wamp.ws/spec#call_message
|
6
|
+
class Version1
|
7
|
+
# @return [Integer] The version of the WAMP protocol defined in this class.
|
8
|
+
def version
|
9
|
+
1
|
10
|
+
end
|
11
|
+
|
12
|
+
# Builds the WELCOME message (server to client)
|
13
|
+
# [ TYPE_ID_WELCOME , sessionId , protocolVersion, serverIdent ]
|
14
|
+
# @param client_id [String] The server generated ID that the WELCOME message
|
15
|
+
# is to be sent to.
|
16
|
+
# @return [String] The WELCOME message as a JSON string.
|
17
|
+
def welcome(id)
|
18
|
+
[type[:WELCOME], id, version, server_ident].to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
# Builds the PREFIX message (client to server)
|
22
|
+
# [ TYPE_ID_PREFIX , prefix , URI ]
|
23
|
+
# @param prefix [String] The shortened CURIE prefix to be registered.
|
24
|
+
# @param uri [String] The full URI to register the prefix to.
|
25
|
+
# @return [String] The PREFIX message as a JSON string.
|
26
|
+
def prefix(prefix, uri)
|
27
|
+
[type[:PREFIX], prefix, uri].to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
# Builds the RPC CALL message (client to server)
|
31
|
+
# [ TYPE_ID_CALL , callID , procURI , ... ]
|
32
|
+
# @param call_id [String] This should be a randomly generated string to
|
33
|
+
# identify the RPC call, the call ID will be returned to the client in the
|
34
|
+
# RPC CALLRESULT or CALLERROR messages.
|
35
|
+
# @param proc_uri [String] The procURI is a string that identifies the remote
|
36
|
+
# procedure to be called and MUST be a valid URI or CURIE.
|
37
|
+
# @param *args [Array] Zero or more additional arguments to be sent with the
|
38
|
+
# CALL message.
|
39
|
+
# @return [String] The CALL message as a JSON string
|
40
|
+
def call(call_id, proc_uri, *args)
|
41
|
+
[type[:CALL], call_id, proc_uri, *args].to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
# Builds the RPC CALLRESULT message (server to client)
|
45
|
+
# [ TYPE_ID_CALLRESULT , callID , result ]
|
46
|
+
# @param call_id [String] This should match the call ID given by the client
|
47
|
+
# by the client.
|
48
|
+
# @param result [String, Integer, Hash, Array] The results of the RPC procedure
|
49
|
+
# initiated by the CALL. Defaults to nil of non is given.
|
50
|
+
# @return [String] The CALLRESULT message as a JSON string.
|
51
|
+
def call_result(call_id, result = nil)
|
52
|
+
[type[:CALLRESULT], call_id, result].to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
# Builds the RPC CALLERROR message (server to client)
|
56
|
+
# [ TYPE_ID_CALLERROR , callID , errorURI , errorDesc , errorDetails(Optional) ]
|
57
|
+
# @param call_id [String] This should match the call ID given by the client
|
58
|
+
# by the client.
|
59
|
+
# @param error_uri [String] A CURIE or URI identifying the error.
|
60
|
+
# @param error_desc [String] A description of the error that occured.
|
61
|
+
# @param error_details [String] Optional. Used to communicate application
|
62
|
+
# error details, defined by the error_uri.
|
63
|
+
# @return [String] The ERRORRESULT message as a JSON string.
|
64
|
+
def call_error(call_id, error_uri, error_desc, error_details = nil)
|
65
|
+
msg = [type[:CALLERROR], call_id, error_uri, error_desc, error_details]
|
66
|
+
msg.delete_if { |x| x.nil? }
|
67
|
+
msg.to_json
|
68
|
+
end
|
69
|
+
|
70
|
+
# Builds the PubSub SUBSCRIBE message (client to server)
|
71
|
+
# [ TYPE_ID_SUBSCRIBE , topicURI ]
|
72
|
+
# @param topic_uri [String] The topic URI or CURIE (from PREFIX) to receive
|
73
|
+
# published events to the given topic.
|
74
|
+
# @return [String] The SUBSCRIBE message as a JSON string.
|
75
|
+
def subscribe(topic_uri)
|
76
|
+
[type[:SUBSCRIBE], topic_uri].to_json
|
77
|
+
end
|
78
|
+
|
79
|
+
# Builds the PubSub UNSUBSCRIBE message (client to server)
|
80
|
+
# [ TYPE_ID_UNSUBSCRIBE , topicURI ]
|
81
|
+
# @param topic_uri [String] The topic URI or CURIE to unsubscribe from.
|
82
|
+
# @return [String] The UNSUBSCRIBE message as a JSON string.
|
83
|
+
def unsubscribe(topic_uri)
|
84
|
+
[type[:UNSUBSCRIBE], topic_uri].to_json
|
85
|
+
end
|
86
|
+
|
87
|
+
# Builds the PubSub PUBLISH message (client to server)
|
88
|
+
# [ TYPE_ID_PUBLISH , topicURI , event ]
|
89
|
+
# [ TYPE_ID_PUBLISH , topicURI , event , excludeMe ]
|
90
|
+
# [ TYPE_ID_PUBLISH , topicURI , event , exclude , eligible ]
|
91
|
+
# @param topic_uri [String] The topic URI or CURIE to publish the event to.
|
92
|
+
# @param payload [String, Array, Hash] The payload to be delivered to the
|
93
|
+
# server.
|
94
|
+
# @param exclude [true, false, Array<String>] Optional. Determines which
|
95
|
+
# clients to exclude from the delivery of the event from the server, you
|
96
|
+
# can give true or false will exclude/include the sending client, or give
|
97
|
+
# an Array of client ID's to exclude.
|
98
|
+
# @param elgible [Array<String>] Optional. An array lf client ID's elgible
|
99
|
+
# to receive the published message.
|
100
|
+
# @return [String] The PUBLISH message as a JSON string.
|
101
|
+
def publish(topic_uri, event, exclude = nil, elgible = nil)
|
102
|
+
msg = [type[:PUBLISH], topic_uri, event]
|
103
|
+
msg[3] = exclude unless exclude.nil?
|
104
|
+
msg[4] = elgible unless elgible.nil?
|
105
|
+
|
106
|
+
msg.to_json
|
107
|
+
end
|
108
|
+
|
109
|
+
# Builds the PubSub EVENT message (server to client)
|
110
|
+
# [ TYPE_ID_EVENT , topicURI , event ]
|
111
|
+
# @param topic_uri [String] The topic URI or CURIE to publish the event to.
|
112
|
+
# @param event [String, Array, Hash] The payload to be delivered to the clients
|
113
|
+
# @return [String] The EVENT message as a JSON string.
|
114
|
+
def event(topic_uri, event)
|
115
|
+
[type[:EVENT], topic_uri, event].to_json
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def server_ident
|
121
|
+
WAMP.identity
|
122
|
+
end
|
123
|
+
|
124
|
+
def type
|
125
|
+
WAMP::MessageType
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/wamp/server.rb
CHANGED
@@ -3,21 +3,26 @@ require 'json'
|
|
3
3
|
|
4
4
|
module WAMP
|
5
5
|
class Server
|
6
|
-
|
6
|
+
include WAMP::Bindable
|
7
7
|
|
8
|
-
|
8
|
+
attr_accessor :options, :topics, :callbacks
|
9
9
|
|
10
10
|
def initialize(options = {})
|
11
|
-
@options
|
12
|
-
@sockets = {}
|
13
|
-
@topics = {}
|
11
|
+
@options = options
|
14
12
|
|
13
|
+
@topics = {}
|
15
14
|
@callbacks = {}
|
15
|
+
@engine = WAMP::Engines::Memory.new
|
16
|
+
@protocol = WAMP::Protocols::Version1.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def available_bindings
|
20
|
+
[:subscribe, :unsubscribe, :publish, :call, :prefix, :connect, :disconnect]
|
16
21
|
end
|
17
22
|
|
18
23
|
def start
|
19
24
|
lambda do |env|
|
20
|
-
|
25
|
+
Faye::WebSocket.load_adapter('thin')
|
21
26
|
if Faye::WebSocket.websocket?(env)
|
22
27
|
ws = Faye::WebSocket.new(env, ['wamp'], ping: 25)
|
23
28
|
|
@@ -33,114 +38,86 @@ module WAMP
|
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
36
|
-
def bind(name, &callback)
|
37
|
-
raise "Invalid callback name: #{name}" unless CALLBACKS.include? name
|
38
|
-
@callbacks[name] = callback
|
39
|
-
end
|
40
|
-
|
41
|
-
def trigger(name, *args)
|
42
|
-
@callbacks[name].call *args if @callbacks[name]
|
43
|
-
end
|
44
|
-
|
45
|
-
def send_event_to_all
|
46
|
-
msg = [8, 'ws://localhost:9292/', "Here I am, Rock you like a hurricane"]
|
47
|
-
@sockets.each_pair do |s, c|
|
48
|
-
s.send msg.to_json
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
41
|
private
|
53
42
|
|
54
43
|
def handle_open(websocket, event)
|
55
|
-
|
44
|
+
client = @engine.create_client(websocket)
|
45
|
+
client.websocket.send @protocol.welcome(client.id)
|
56
46
|
|
57
|
-
trigger(:connect,
|
47
|
+
trigger(:connect, client)
|
58
48
|
end
|
59
49
|
|
60
50
|
def handle_message(websocket, event)
|
61
|
-
|
51
|
+
client = @engine.find_clients(websocket: websocket).first
|
62
52
|
|
63
|
-
|
64
|
-
msg_type
|
53
|
+
data = JSON.parse(event.data)
|
54
|
+
msg_type = data.shift
|
65
55
|
|
66
56
|
case WAMP::MessageType[msg_type]
|
67
57
|
when :PREFIX
|
68
|
-
|
69
|
-
uri = parsed_msg[2]
|
70
|
-
|
71
|
-
socket.add_prefix(prefix, uri)
|
72
|
-
|
73
|
-
trigger(:prefix, socket, prefix, uri)
|
58
|
+
handle_prefix(client, data)
|
74
59
|
when :CALL
|
75
|
-
|
60
|
+
handle_call(client, data)
|
76
61
|
when :SUBSCRIBE
|
77
|
-
handle_subscribe(
|
62
|
+
handle_subscribe(client, data)
|
78
63
|
when :UNSUBSCRIBE
|
79
|
-
|
80
|
-
topic = @topics[topic_name]
|
81
|
-
|
82
|
-
trigger(:unsubscribe, socket, topic.name)
|
64
|
+
handle_unsubscribe(client, data)
|
83
65
|
when :PUBLISH
|
84
|
-
handle_publish(
|
66
|
+
handle_publish(client, data)
|
85
67
|
end
|
86
68
|
end
|
87
69
|
|
88
|
-
# Handle a
|
89
|
-
#
|
90
|
-
def
|
91
|
-
|
70
|
+
# Handle a prefix message from a client
|
71
|
+
# PREFIX data structure [PREFIX, URI]
|
72
|
+
def handle_prefix(client, data)
|
73
|
+
prefix, uri = data
|
92
74
|
|
93
|
-
|
94
|
-
|
75
|
+
topic = @engine.find_or_create_topic(uri)
|
76
|
+
client.add_prefix(prefix, topic)
|
95
77
|
|
96
|
-
trigger(:
|
78
|
+
trigger(:prefix, client, prefix, uri)
|
97
79
|
end
|
98
80
|
|
99
|
-
# Handle
|
100
|
-
#
|
101
|
-
def
|
102
|
-
|
103
|
-
|
81
|
+
# Handle RPC call message from a client
|
82
|
+
# CALL data structure [callID, procURI, ... ]
|
83
|
+
def handle_call(client, data)
|
84
|
+
call_id, proc_uri, *args = data
|
85
|
+
|
86
|
+
trigger(:call, client, call_id, proc_uri, args)
|
87
|
+
end
|
104
88
|
|
105
|
-
|
89
|
+
# Handle a subscribe message from a client
|
90
|
+
# SUBSCRIBE data structure [TOPIC]
|
91
|
+
def handle_subscribe(client, data)
|
92
|
+
topic = @engine.subscribe_client_to_topic client, data[0]
|
106
93
|
|
107
|
-
trigger(:
|
94
|
+
trigger(:subscribe, client, topic.uri)
|
108
95
|
end
|
109
96
|
|
110
|
-
# Handle
|
111
|
-
#
|
112
|
-
def
|
113
|
-
topic
|
114
|
-
payload = data[2]
|
115
|
-
exclude = data[3]
|
116
|
-
include = data[4]
|
117
|
-
|
118
|
-
if exclude == true
|
119
|
-
exclude = [socket.id]
|
120
|
-
elsif exclude == false
|
121
|
-
exclude = []
|
122
|
-
end
|
97
|
+
# Handle an unsubscribe message from client
|
98
|
+
# UNSUBSCRIBE data structure [TOPIC]
|
99
|
+
def handle_unsubscribe(client, data)
|
100
|
+
topic = @engine.unsubscribe_client_from_topic(client, data[0])
|
123
101
|
|
124
|
-
|
125
|
-
|
126
|
-
next if exclude.include? k
|
102
|
+
trigger(:unsubscribe, client, topic.uri)
|
103
|
+
end
|
127
104
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
105
|
+
# Handle a message published by a client
|
106
|
+
# PUBLISH data structure [TOPIC, DATA, EXCLUDE, INCLUDE]
|
107
|
+
def handle_publish(client, data)
|
108
|
+
topic_name, payload, exclude, include = data
|
109
|
+
topic = @engine.find_or_create_topic(topic_name)
|
132
110
|
|
133
|
-
|
111
|
+
topic.publish(client, @protocol, payload, exclude, include)
|
134
112
|
|
135
|
-
trigger(:publish,
|
113
|
+
trigger(:publish, client, topic.uri, payload, exclude, include)
|
136
114
|
end
|
137
115
|
|
138
116
|
def handle_close(websocket, event)
|
139
|
-
|
140
|
-
|
141
|
-
p [socket.id, :close, event.code, event.reason]
|
117
|
+
# client = @engine.find_clients(websocket: websocket).first
|
118
|
+
client = @engine.delete_client(websocket)
|
142
119
|
|
143
|
-
trigger(:disconnect,
|
120
|
+
trigger(:disconnect, client)
|
144
121
|
end
|
145
122
|
end
|
146
123
|
end
|
data/lib/wamp/socket.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
require 'securerandom'
|
2
1
|
require 'json'
|
3
2
|
|
4
3
|
module WAMP
|
5
4
|
class Socket
|
6
5
|
attr_accessor :id, :websocket, :topics, :prefixes
|
7
6
|
|
8
|
-
def initialize(websocket)
|
9
|
-
@id =
|
7
|
+
def initialize(id, websocket)
|
8
|
+
@id = id
|
10
9
|
@topics = []
|
11
10
|
@prefixes = {}
|
12
11
|
@websocket = websocket
|
@@ -30,7 +29,7 @@ module WAMP
|
|
30
29
|
|
31
30
|
def send_welcome_message
|
32
31
|
welcome_msg = [WAMP::MessageType[:WELCOME], id, WAMP.protocol_version, WAMP.identity]
|
33
|
-
websocket.send welcome_msg.to_json
|
32
|
+
# websocket.send welcome_msg.to_json
|
34
33
|
end
|
35
34
|
end
|
36
35
|
end
|
data/lib/wamp/topic.rb
CHANGED
@@ -1,9 +1,38 @@
|
|
1
1
|
module WAMP
|
2
2
|
class Topic
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :uri, :clients
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(uri)
|
6
|
+
@uri = uri
|
7
|
+
@clients = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_client(client)
|
11
|
+
clients << client unless clients.include? client
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_client(client)
|
15
|
+
clients.delete client
|
16
|
+
end
|
17
|
+
|
18
|
+
def publish(client, protocol, payload, excluded, included)
|
19
|
+
rec_clients = clients.dup
|
20
|
+
|
21
|
+
if excluded == true
|
22
|
+
excluded = [client.id]
|
23
|
+
elsif excluded == false || excluded.nil?
|
24
|
+
excluded = []
|
25
|
+
end
|
26
|
+
|
27
|
+
rec_clients.delete_if { |c| excluded.include? c.id }
|
28
|
+
|
29
|
+
if included
|
30
|
+
rec_clients.delete_if { |c| !included.include?(c.id) }
|
31
|
+
end
|
32
|
+
|
33
|
+
rec_clients.each do |c|
|
34
|
+
c.websocket.send protocol.event(self.uri, payload)
|
35
|
+
end
|
7
36
|
end
|
8
37
|
end
|
9
38
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WAMP::Engines::Memory do
|
4
|
+
let(:memory) { WAMP::Engines::Memory.new }
|
5
|
+
|
6
|
+
context "#initialization" do
|
7
|
+
it "should initialize with some empty hashes" do
|
8
|
+
expect(memory.clients).to eq({})
|
9
|
+
expect(memory.topics).to eq({})
|
10
|
+
expect(memory.options).to eq({})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "client management" do
|
15
|
+
context "#create_client" do
|
16
|
+
it "should create a new instance of a socket" do
|
17
|
+
expect { memory.create_client(DummySocket) }
|
18
|
+
.to change(memory.clients, :size).by(1)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with existing clients" do
|
23
|
+
before do
|
24
|
+
@ds1 = DummySocket.new(1)
|
25
|
+
@ds2 = DummySocket.new(2)
|
26
|
+
|
27
|
+
@id1 = memory.create_client(@ds1).id
|
28
|
+
@id2 = memory.create_client(@ds2).id
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#delete_client" do
|
32
|
+
it "should delete a client" do
|
33
|
+
expect { memory.delete_client @ds1 }
|
34
|
+
.to change(memory.clients, :size).by(-1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return the removed client" do
|
38
|
+
client = memory.find_clients(websocket: @ds1).first
|
39
|
+
expect(memory.delete_client @ds1).to eq client
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#all_clients" do
|
44
|
+
it "should return an array of all the clients" do
|
45
|
+
expect(memory.all_clients.size).to eq 2
|
46
|
+
|
47
|
+
expect(memory.all_clients).to eq memory.clients.values
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "#find_clients" do
|
52
|
+
it "should find a client by the given parameter" do
|
53
|
+
expect(memory.find_clients(id: @id1).first.websocket).to eq @ds1
|
54
|
+
expect(memory.find_clients(id: @id2).first.websocket).to eq @ds2
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return all the clients that match" do
|
58
|
+
expect(memory.find_clients(topics: []).size).to eq 2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "topic management" do
|
65
|
+
context "find or create a new topic" do
|
66
|
+
it "should create a new topic" do
|
67
|
+
expect { memory.find_or_create_topic("http://localhost/sample_topic") }
|
68
|
+
.to change(memory.topics, :size).by(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return an existing topic if one already exists" do
|
72
|
+
new_topic = memory.find_or_create_topic("http://localhost/sample_topic")
|
73
|
+
expect(memory.find_or_create_topic "http://localhost/sample_topic")
|
74
|
+
.to eq new_topic
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "topic subscription" do
|
79
|
+
before do
|
80
|
+
@client = memory.create_client(@ds1)
|
81
|
+
@topic = memory.find_or_create_topic("http://localhost/sample_topic")
|
82
|
+
|
83
|
+
@sub = memory.subscribe_client_to_topic @client, @topic.uri
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should add a client to a topic" do
|
87
|
+
expect(@client.topics).to include @topic
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should add a topic to a client" do
|
91
|
+
expect(@topic.clients).to include @client
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should return the topic the client subscribed to" do
|
95
|
+
expect(@sub).to eq @topic
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "topic unsubscription" do
|
100
|
+
before do
|
101
|
+
@client = memory.create_client(@ds1)
|
102
|
+
@topic = memory.find_or_create_topic("http://localhost/sample_topic")
|
103
|
+
|
104
|
+
@sub = memory.subscribe_client_to_topic @client, @topic.uri
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should remove the client from the topic" do
|
108
|
+
expect { memory.unsubscribe_client_from_topic @client, @topic.uri }
|
109
|
+
.to change(@topic.clients, :size).by(-1)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should remove the topic from the client" do
|
113
|
+
expect { memory.unsubscribe_client_from_topic @client, @topic.uri }
|
114
|
+
.to change(@client.topics, :size).by(-1)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|