wamp_server 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/rubencaro/wamp_server.png?branch=master)](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
|