wamp_server 0.1
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 +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/LICENSE +674 -0
- data/README.md +8 -0
- data/Rakefile +9 -0
- data/lib/version.rb +3 -0
- data/lib/wamp_server.rb +122 -0
- data/test/log_helpers.rb +104 -0
- data/test/main_test.rb +187 -0
- data/test/test_app/helpers.rb +11 -0
- data/test/test_app/main.rb +48 -0
- data/test/test_app/memory.rb +60 -0
- data/test/test_app/test_app.rb +64 -0
- data/test/test_app/test_controller.rb +11 -0
- data/test/test_helper.rb +202 -0
- data/wamp_server.gemspec +28 -0
- metadata +141 -0
data/README.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
wamp_server
|
2
|
+
===========
|
3
|
+
|
4
|
+
[](http://travis-ci.org/rubencaro/wamp_server)
|
5
|
+
|
6
|
+
[WAMPv1](http://wamp.ws/spec) compliant server to be used
|
7
|
+
as a template for nice and shining Ruby apps, based on
|
8
|
+
[WebSocket EventMachine Server](https://github.com/imanel/websocket-eventmachine-server).
|
data/Rakefile
ADDED
data/lib/version.rb
ADDED
data/lib/wamp_server.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'websocket-eventmachine-server'
|
2
|
+
require 'json'
|
3
|
+
require_relative 'version'
|
4
|
+
|
5
|
+
#
|
6
|
+
# see http://wamp.ws/spec
|
7
|
+
#
|
8
|
+
module WAMP
|
9
|
+
|
10
|
+
WELCOME = 0
|
11
|
+
PREFIX = 1
|
12
|
+
CALL = 2
|
13
|
+
CALLRESULT = 3
|
14
|
+
CALLERROR = 4
|
15
|
+
SUBSCRIBE = 5
|
16
|
+
UNSUBSCRIBE = 6
|
17
|
+
PUBLISH = 7
|
18
|
+
EVENT = 8
|
19
|
+
|
20
|
+
RESPONSES = { :not_json => { :error => 'Message is not JSON !' }.to_json }
|
21
|
+
|
22
|
+
def self.stamp
|
23
|
+
"#{self.name} #{VERSION}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.stop_server
|
27
|
+
puts "Terminating WebSocket Server"
|
28
|
+
EM.stop
|
29
|
+
end
|
30
|
+
|
31
|
+
# Start a new Server
|
32
|
+
#
|
33
|
+
# You should pass some callbacks:
|
34
|
+
#
|
35
|
+
# :before_start => To be called before actually starting the server, but already within EM reactor.
|
36
|
+
# :onwelcome => To be called for WELCOME calls.
|
37
|
+
# :onprefix => To be called for PREFIX calls.
|
38
|
+
# :oncall => To be called when server receives a CALL call.
|
39
|
+
# :onpublish => To be called when server receives a PUBLISH call.
|
40
|
+
# :onclose => To be called when a client closes connection.
|
41
|
+
# :onsubscribe => Called for SUBSCRIBE calls.
|
42
|
+
# :onunsubscribe => On UNSUBSCRIBE calls.
|
43
|
+
#
|
44
|
+
def self.start_server(a_module, opts = {})
|
45
|
+
|
46
|
+
# avoid more instances
|
47
|
+
a = a_module
|
48
|
+
a.extend a
|
49
|
+
|
50
|
+
host = opts[:host] || '0.0.0.0'
|
51
|
+
port = opts[:port] || '3000'
|
52
|
+
|
53
|
+
EM.run do
|
54
|
+
|
55
|
+
trap("INT") { WAMP.stop_server }
|
56
|
+
trap("TERM") { WAMP.stop_server }
|
57
|
+
trap("KILL") { WAMP.stop_server }
|
58
|
+
|
59
|
+
a.before_start if a.respond_to?(:before_start)
|
60
|
+
|
61
|
+
puts "Listening on #{host}:#{port}"
|
62
|
+
WebSocket::EventMachine::Server.start(:host => host, :port => port) do |ws|
|
63
|
+
|
64
|
+
ws.onopen do
|
65
|
+
Fiber.new do
|
66
|
+
sid = a.onwelcome ws: ws
|
67
|
+
ws.send [WAMP::WELCOME, sid, 1, WAMP.stamp].to_json
|
68
|
+
end.resume
|
69
|
+
end
|
70
|
+
|
71
|
+
ws.onmessage do |msg, type|
|
72
|
+
Fiber.new do
|
73
|
+
begin
|
74
|
+
call = JSON.parse msg
|
75
|
+
rescue JSON::ParserError
|
76
|
+
ws.send( WAMP::RESPONSES[:not_json] )
|
77
|
+
end
|
78
|
+
|
79
|
+
if call.first == WAMP::CALL then
|
80
|
+
# [ TYPE_ID_CALL , callID , procURI , ... ]
|
81
|
+
_, callid, curie, *args = call
|
82
|
+
begin
|
83
|
+
result = a.oncall ws: ws, curie: curie, args: args
|
84
|
+
ws.send [WAMP::CALLRESULT, callid, result].to_json
|
85
|
+
rescue => ex
|
86
|
+
ws.send [WAMP::CALLERROR, callid, "http://error", ex.to_s].to_json
|
87
|
+
end
|
88
|
+
elsif call.first == WAMP::PUBLISH then
|
89
|
+
# [ TYPE_ID_PUBLISH , topicURI , event , exclude , eligible ]
|
90
|
+
_, curie, event, excluded, eligible = call
|
91
|
+
result = a.onpublish curie: curie
|
92
|
+
dest = (result[:subscribed] & eligible) - excluded
|
93
|
+
package = [WAMP::EVENT, result[:uri], event].to_json
|
94
|
+
dest.each do |d|
|
95
|
+
ObjectSpace._id2ref(d).send( package )
|
96
|
+
end
|
97
|
+
elsif call.first == WAMP::SUBSCRIBE then
|
98
|
+
# [ TYPE_ID_SUBSCRIBE , topicURI ]
|
99
|
+
a.onsubscribe ws: ws, curie: call[1]
|
100
|
+
elsif call.first == WAMP::UNSUBSCRIBE then
|
101
|
+
# [ TYPE_ID_UNSUBSCRIBE , topicURI ]
|
102
|
+
a.onunsubscribe ws: ws, curie: call[1]
|
103
|
+
elsif call.first == WAMP::PREFIX then
|
104
|
+
# [ TYPE_ID_PREFIX , prefix, URI ]
|
105
|
+
_, prefix, uri = call
|
106
|
+
a.onprefix ws: ws, uri: uri, prefix: prefix
|
107
|
+
end
|
108
|
+
end.resume
|
109
|
+
end
|
110
|
+
|
111
|
+
ws.onclose do
|
112
|
+
Fiber.new do
|
113
|
+
a.onclose ws: ws
|
114
|
+
end.resume
|
115
|
+
end
|
116
|
+
|
117
|
+
end # Server.start
|
118
|
+
end # EM.run
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/test/log_helpers.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
module Helpers
|
3
|
+
module Log
|
4
|
+
|
5
|
+
@@next_color = 0 # for alternate
|
6
|
+
@@palette = [:dark_gray, :light_gray]
|
7
|
+
|
8
|
+
def log(msg, opts = {})
|
9
|
+
|
10
|
+
if not opts[:clean] then
|
11
|
+
opts[:location] ||= caller_locations(1,1)[0].label # much faster than 'caller'
|
12
|
+
msg = "[#{opts[:location]}] #{msg}"
|
13
|
+
end
|
14
|
+
|
15
|
+
if opts[:color] then
|
16
|
+
msg = send(opts[:color],msg)
|
17
|
+
end
|
18
|
+
|
19
|
+
msg = "[#{Time.now.strftime('%F %T')}]" + msg if not opts[:clean]
|
20
|
+
puts msg
|
21
|
+
$stdout.flush # as soon as possible
|
22
|
+
end
|
23
|
+
|
24
|
+
def log_ex(ex, opts = {})
|
25
|
+
trace = opts[:trace] || true
|
26
|
+
msg = opts[:msg] || ''
|
27
|
+
msg += light_purple(" \n Exception: #{ex} \n ")
|
28
|
+
msg << purple(" Backtrace: #{ex.backtrace.join("\n")} ") if trace
|
29
|
+
log msg
|
30
|
+
end
|
31
|
+
|
32
|
+
def spit(msg, opts = {}) # allow hashes as msg
|
33
|
+
opts[:color] ||= :light_red
|
34
|
+
opts[:clean] = true
|
35
|
+
l = caller_locations(1,1)[0]
|
36
|
+
log "\n[#{l.label}:#{l.lineno}] \n " + msg.inspect + " \n ", opts
|
37
|
+
end
|
38
|
+
|
39
|
+
def announce(msg = nil, opts = {})
|
40
|
+
label = caller_locations(1,1)[0].label
|
41
|
+
place = File.basename(caller_locations(1,1)[0].path) + ":" + caller_locations(1,1)[0].lineno.to_s
|
42
|
+
msg ||= "\n ==> Entering #{label} (#{place})..."
|
43
|
+
opts[:color] ||= :cyan
|
44
|
+
opts[:clean] = true
|
45
|
+
log msg, opts
|
46
|
+
end
|
47
|
+
|
48
|
+
def yellow(str)
|
49
|
+
" \033[1;33m " + str + " \033[00m "
|
50
|
+
end
|
51
|
+
|
52
|
+
def cyan(str)
|
53
|
+
" \033[0;36m " + str + " \033[00m "
|
54
|
+
end
|
55
|
+
|
56
|
+
def light_cyan(str)
|
57
|
+
" \033[1;36m " + str + " \033[00m "
|
58
|
+
end
|
59
|
+
|
60
|
+
def purple(str)
|
61
|
+
" \033[0;35m " + str + " \033[00m "
|
62
|
+
end
|
63
|
+
|
64
|
+
def light_purple(str)
|
65
|
+
" \033[1;35m " + str + " \033[00m "
|
66
|
+
end
|
67
|
+
|
68
|
+
def brown(str)
|
69
|
+
" \033[0;33m " + str + " \033[00m "
|
70
|
+
end
|
71
|
+
|
72
|
+
def red(str)
|
73
|
+
" \033[0;31m " + str + " \033[00m "
|
74
|
+
end
|
75
|
+
|
76
|
+
def light_red(str)
|
77
|
+
" \033[1;31m " + str + " \033[00m "
|
78
|
+
end
|
79
|
+
|
80
|
+
def light_gray(str)
|
81
|
+
" \033[0;37m " + str + " \033[00m "
|
82
|
+
end
|
83
|
+
|
84
|
+
def dark_gray(str)
|
85
|
+
" \033[1;30m " + str + " \033[00m "
|
86
|
+
end
|
87
|
+
|
88
|
+
def white(str)
|
89
|
+
" \033[1;37m " + str + " \033[00m "
|
90
|
+
end
|
91
|
+
|
92
|
+
def alternate(str)
|
93
|
+
color = @@palette[@@next_color]
|
94
|
+
@@next_color += 1
|
95
|
+
@@next_color = 0 if @@next_color >= @@palette.size
|
96
|
+
send(color,str)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
extend Log
|
102
|
+
end
|
103
|
+
|
104
|
+
H = Helpers if not defined? H
|
data/test/main_test.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require_relative '../lib/wamp_server'
|
3
|
+
|
4
|
+
class TestMain < TestCase
|
5
|
+
|
6
|
+
def test_plain_ws
|
7
|
+
cb = lambda do |ws,msg,type,info|
|
8
|
+
ws.close
|
9
|
+
end
|
10
|
+
info = run_ws_client onmessage: cb
|
11
|
+
assert_equal 1, info[:messages].count
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_welcome
|
15
|
+
cb = lambda do |ws,msg,type,info|
|
16
|
+
data = check_is_json msg
|
17
|
+
check_is_welcome data
|
18
|
+
result = call(ws,'get_db','sessions')
|
19
|
+
assert_equal 1, result.count, "#{result}"
|
20
|
+
assert result.first['_id'], result
|
21
|
+
ws.close
|
22
|
+
end
|
23
|
+
info = run_ws_client onmessage: cb
|
24
|
+
assert_equal 1, info[:messages].count, info
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_prefix
|
28
|
+
cb = lambda do |ws,msg,type,info|
|
29
|
+
data = check_is_json msg
|
30
|
+
|
31
|
+
assert_equal WAMP::WELCOME, data.first, "#{data}"
|
32
|
+
|
33
|
+
uri = "http://test#"
|
34
|
+
prefix = 'test'
|
35
|
+
|
36
|
+
ws.send [WAMP::PREFIX, prefix, uri].to_json
|
37
|
+
result = call(ws,'get_db','sessions')
|
38
|
+
assert_equal 1, result.count, result
|
39
|
+
assert_equal uri, result.first['prefixes'][prefix], "#{result}"
|
40
|
+
ws.close
|
41
|
+
end
|
42
|
+
info = run_ws_client onmessage: cb
|
43
|
+
assert_equal 1, info[:messages].count, info
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_rpc
|
47
|
+
controller = 'test' # TestController
|
48
|
+
action = 'get_db' # get_db_action
|
49
|
+
args = ['sessions'] # 'table' argument for get_db_action
|
50
|
+
callid = 'right one'
|
51
|
+
cmd = [WAMP::CALL, callid, "http://#{controller}##{action}"] + args
|
52
|
+
|
53
|
+
cb = lambda do |ws,msg,type,info|
|
54
|
+
data = check_is_json msg
|
55
|
+
if data.first == WAMP::WELCOME then
|
56
|
+
# send the right request right after welcome
|
57
|
+
ws.send cmd.to_json
|
58
|
+
elsif data.first == WAMP::CALLRESULT then
|
59
|
+
# assert the right answer for the right request
|
60
|
+
assert_equal 'right one', data[1], data
|
61
|
+
result = data.last
|
62
|
+
assert_equal 1, result.count, "#{result}"
|
63
|
+
assert result.first['_id'], result
|
64
|
+
|
65
|
+
# send the wrong request to force error
|
66
|
+
callid = 'wrong one'
|
67
|
+
controller = 'not_existing'
|
68
|
+
cmd = [WAMP::CALL, callid, "http://#{controller}##{action}"] + args
|
69
|
+
ws.send cmd.to_json
|
70
|
+
else # WAMP::CALLERROR
|
71
|
+
# assert the right answer for the wrong request
|
72
|
+
assert_equal WAMP::CALLERROR, data.first, "#{data}"
|
73
|
+
assert_equal 'wrong one', data[1], data
|
74
|
+
assert data.last =~ /Not Found/
|
75
|
+
ws.close
|
76
|
+
end
|
77
|
+
end
|
78
|
+
info = run_ws_client onmessage: cb
|
79
|
+
assert_equal 3, info[:messages].count, info
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_subscribe_unsubscribe
|
83
|
+
|
84
|
+
uri = 'http://test/subscribe_test'
|
85
|
+
|
86
|
+
cb = lambda do |ws,msg,type,info|
|
87
|
+
data = check_is_json msg
|
88
|
+
assert_equal WAMP::WELCOME, data.first, data
|
89
|
+
|
90
|
+
# send the subscribe request
|
91
|
+
ws.send [WAMP::SUBSCRIBE,uri].to_json
|
92
|
+
|
93
|
+
# ensure it's subscribed
|
94
|
+
# subscription is saved within the session
|
95
|
+
# in a hash where the uri is the key
|
96
|
+
result = call(ws,'get_db','sessions')
|
97
|
+
session = result.select{|r| r['_id'] == data[1]}.first
|
98
|
+
assert session, "#{result} \n #{data}"
|
99
|
+
subs = session['subscriptions']
|
100
|
+
assert subs && (subs.count == 1), session
|
101
|
+
assert subs[uri], subs
|
102
|
+
|
103
|
+
# send the unsubscribe request
|
104
|
+
ws.send [WAMP::UNSUBSCRIBE,uri].to_json
|
105
|
+
|
106
|
+
# ensure it's subscribed
|
107
|
+
result = call(ws,'get_db','sessions')
|
108
|
+
session = result.select{|r| r['_id'] == data[1]}.first
|
109
|
+
subs = session['subscriptions']
|
110
|
+
assert subs && subs.none?, session
|
111
|
+
|
112
|
+
ws.close
|
113
|
+
end
|
114
|
+
info = run_ws_client onmessage: cb
|
115
|
+
assert_equal 1, info[:messages].count, info
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_publish
|
119
|
+
|
120
|
+
sat_info = { :sats => {}, :sent => 0, :received => {} }
|
121
|
+
n = 5
|
122
|
+
uri = 'http://test/subscribe_test'
|
123
|
+
lucky_ones = banned_ones = []
|
124
|
+
|
125
|
+
cb = lambda do |ws,msg,type,info|
|
126
|
+
data = check_is_json msg
|
127
|
+
assert_equal WAMP::WELCOME, data.first, data
|
128
|
+
sid = data[1]
|
129
|
+
|
130
|
+
# run n satellite clients and subscribe them to our topic
|
131
|
+
subscribe_cb = lambda do |ws_sat,msg,type,info|
|
132
|
+
data = check_is_json msg
|
133
|
+
if data.first == WAMP::WELCOME then
|
134
|
+
sat_info[:sats][data[1]] = ws_sat # save it to be closed afterwards
|
135
|
+
ws_sat.send [WAMP::SUBSCRIBE,uri].to_json
|
136
|
+
sat_info[:sent] += 1
|
137
|
+
elsif data.first == WAMP::EVENT then
|
138
|
+
sat_info[:received][ws_sat.object_id] ||= []
|
139
|
+
sat_info[:received][ws_sat.object_id] << msg
|
140
|
+
end
|
141
|
+
end
|
142
|
+
n.times do
|
143
|
+
run_satellite_ws_client info, :onmessage => subscribe_cb
|
144
|
+
end
|
145
|
+
|
146
|
+
# ensure we have audience
|
147
|
+
wait_for { sat_info[:sent] >= n } # until everyone makes the request
|
148
|
+
sessions = call(ws,'get_db','sessions')
|
149
|
+
assert_equal n+1, sessions.count, sessions
|
150
|
+
sessions.reject{|s| s['_id'] == sid}.each do |s|
|
151
|
+
subs = s['subscriptions']
|
152
|
+
assert subs && (subs.count == 1), s
|
153
|
+
assert subs[uri], subs
|
154
|
+
end
|
155
|
+
|
156
|
+
# publish only within the lucky_ones, but not the banned_ones
|
157
|
+
lucky_ones = sat_info[:sats].keys[0..-1] # all but last are allowed
|
158
|
+
banned_ones = sat_info[:sats].keys[0..0] # first is banned
|
159
|
+
ws.send [WAMP::PUBLISH,uri,'hello',banned_ones,lucky_ones].to_json
|
160
|
+
|
161
|
+
# wait until everyone get the event
|
162
|
+
wait_for { sat_info[:received].count >= (lucky_ones - banned_ones).count }
|
163
|
+
|
164
|
+
# test is received by the lucky_ones, not the banned_ones
|
165
|
+
int = sat_info[:received].keys & lucky_ones
|
166
|
+
assert_equal 0, int.count, int
|
167
|
+
|
168
|
+
sat_info[:sats].values.each{ |s| s.close }
|
169
|
+
ws.close
|
170
|
+
end
|
171
|
+
info = run_ws_client onmessage: cb
|
172
|
+
|
173
|
+
# test is published to the lucky_ones, not the banned_ones
|
174
|
+
events = info[:messages].select{ |i| i.first == WAMP::EVENT }
|
175
|
+
assert_equal (lucky_ones - banned_ones).count, events.count, events
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def check_is_welcome(data)
|
181
|
+
# [ TYPE_ID_WELCOME , sessionId , protocolVersion, serverIdent ]
|
182
|
+
assert_equal 4, data.count
|
183
|
+
assert_equal WAMP::WELCOME, data[0]
|
184
|
+
assert_equal 1, data[2] # protocol version
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|