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.
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require_relative '../../lib/wamp_server'
4
+ require_relative 'test_app'
5
+
6
+ module MyServer
7
+
8
+ def before_start
9
+ TestApp.init
10
+ end
11
+
12
+ def onprefix(opts)
13
+ TestApp.save_prefix opts[:ws], opts[:prefix], opts[:uri]
14
+ end
15
+
16
+ def onwelcome(opts)
17
+ TestApp.init_session opts[:ws]
18
+ end
19
+
20
+ def oncall(opts)
21
+ uri = TestApp.solve_uri opts[:ws], opts[:curie]
22
+ controller, action = TestApp.parse_uri uri
23
+ TestApp.route(controller, action, *opts[:args])
24
+ end
25
+
26
+ def onpublish(opts)
27
+ uri = TestApp.solve_uri opts[:ws], opts[:curie]
28
+ subscribed = TestApp.get_suscriptions uri
29
+ { :uri => uri, :subscribed => subscribed }
30
+ end
31
+
32
+ def onsubscribe(opts)
33
+ uri = TestApp.solve_uri opts[:ws], opts[:curie]
34
+ TestApp.subscribe opts[:ws], uri
35
+ end
36
+
37
+ def onunsubscribe(opts)
38
+ uri = TestApp.solve_uri opts[:ws], opts[:curie]
39
+ TestApp.unsubscribe opts[:ws], uri
40
+ end
41
+
42
+ def onclose(opts)
43
+ TestApp.remove_session opts[:ws]
44
+ end
45
+
46
+ end
47
+
48
+ WAMP.start_server MyServer
@@ -0,0 +1,60 @@
1
+ module TestApp
2
+ module Drivers
3
+ class Memory
4
+ @@db = {}
5
+
6
+ def self.get_db(table); @@db[table].map{|k,v| v[:_id] = k; v};end
7
+
8
+ def self.init
9
+ @@db = {}
10
+ clear_sessions
11
+ end
12
+
13
+ def self.init_session(ws)
14
+ @@db['sessions'] ||= {}
15
+ @@db['sessions'][ws.object_id] = {}
16
+ ws.object_id
17
+ end
18
+
19
+ def self.save_prefix(ws,prefix,uri)
20
+ @@db['sessions'][ws.object_id][:prefixes] ||= {}
21
+ @@db['sessions'][ws.object_id][:prefixes][prefix] = uri
22
+ end
23
+
24
+ def self.solve_uri(ws,uri)
25
+ return uri if @@db['sessions'][ws.object_id].nil? or
26
+ @@db['sessions'][ws.object_id][:prefixes].nil?
27
+
28
+ prefix, action = uri.split(':') # 'prefix:action'
29
+ solved = @@db['sessions'][ws.object_id][:prefixes][prefix]
30
+ solved ? "#{solved}#{action}" : uri
31
+ end
32
+
33
+ def self.clear_sessions
34
+ @@db['sessions'] = {}
35
+ end
36
+
37
+ def self.remove_session(ws)
38
+ @@db['sessions'].delete ws.object_id
39
+ end
40
+
41
+ def self.subscribe(ws,uri)
42
+ @@db['sessions'][ws.object_id][:subscriptions] ||= {}
43
+ @@db['sessions'][ws.object_id][:subscriptions][uri] = {}
44
+ end
45
+
46
+ def self.unsubscribe(ws,uri)
47
+ return if @@db['sessions'][ws.object_id][:subscriptions].nil?
48
+ @@db['sessions'][ws.object_id][:subscriptions].delete uri
49
+ end
50
+
51
+ def self.get_suscriptions(uri)
52
+ @@db['sessions'].select do |id,sess|
53
+ subs = sess[:subscriptions]
54
+ subs and subs[uri]
55
+ end.keys
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ require 'forwardable'
2
+ require_relative 'helpers'
3
+ require_relative 'memory'
4
+ require_relative 'test_controller'
5
+
6
+ module TestApp
7
+ extend SingleForwardable
8
+
9
+ def self.delegate
10
+ def_delegators @@driver, :init_session, :save_prefix, :solve_uri,
11
+ :clear_sessions, :remove_session,
12
+ :subscribe, :unsubscribe, :get_suscriptions
13
+ end
14
+
15
+ def self.driver; @@driver; end
16
+
17
+ def self.init(driver = TestApp::Drivers::Memory)
18
+ @@driver = driver
19
+ delegate
20
+ @@driver.init
21
+ @@driver.clear_sessions
22
+ @@routes = {}
23
+ fill_routes @@routes
24
+ end
25
+
26
+ =begin
27
+ Rellena el hash routes usando todos los constants *Controller como primer nivel
28
+ y los métodos *_action como segundo nivel, y guardando el método como valor final:
29
+
30
+ routes = {
31
+ 'a_controller' => {
32
+ 'a_action' => AController.a_action
33
+ }
34
+ }
35
+
36
+ El enrutamiento se reduce a buscar una key en un hash.
37
+ =end
38
+ def self.fill_routes(routes)
39
+ controllers = TestApp.constants.grep /^.+Controller$/
40
+ controllers.each do |c|
41
+ controller = TestApp.const_get(c)
42
+ c = c.to_s.sub(/Controller$/,'')
43
+ actions = controller.methods.grep(/^.+_action$/)
44
+ actions.each do |a|
45
+ routes[c.to_s.underscore] ||= {}
46
+ routes[c.to_s.underscore][a.to_s.sub(/_action$/,'')] = controller.method(a)
47
+ end
48
+ end
49
+ end
50
+
51
+ def self.route(controller, action, *args)
52
+ if @@routes[controller].nil? or
53
+ @@routes[controller][action].nil? then
54
+ raise "Not Found #{controller}##{action}"
55
+ end
56
+
57
+ return @@routes[controller][action].call(*args)
58
+ end
59
+
60
+ def self.parse_uri(uri)
61
+ uri =~ %r_http://([^#]+)#(.+)_
62
+ [$1, $2]
63
+ end
64
+ end
@@ -0,0 +1,11 @@
1
+ module TestApp
2
+ module TestController
3
+ def self.get_db_action(*args)
4
+ TestApp.driver.get_db(*args)
5
+ end
6
+
7
+ def self.subscribe_test(*args)
8
+ TestApp.driver.subscribe(*args)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,202 @@
1
+ require 'minitest/pride'
2
+ require 'minitest/autorun'
3
+ require 'websocket-eventmachine-client'
4
+ require 'pty'
5
+ require 'socket'
6
+ require 'timeout'
7
+ require 'json'
8
+ require 'em-synchrony'
9
+
10
+ require_relative 'log_helpers'
11
+
12
+ class TestCase < Minitest::Test
13
+
14
+ # hijack a satellite client to be the main client
15
+ def run_ws_client(opts = {})
16
+
17
+ info = {} # hash to gather info
18
+
19
+ prev_onclose = opts[:onclose]
20
+ opts[:onclose] = lambda do |ws,info|
21
+ prev_onclose.call(ws,info) if prev_onclose
22
+ EM.stop # stop after this client closes
23
+ end
24
+
25
+ EM.run do
26
+ run_satellite_ws_client info, opts
27
+ end
28
+ info
29
+ end
30
+
31
+ # run a WebSocket::EventMachine::Client assuming to be a satellite client,
32
+ # i.e. inside an EM.run loop and not stopping it
33
+ def run_satellite_ws_client(info, opts = {})
34
+
35
+ opts[:onopen] ||= lambda{ |ws,info| }
36
+ opts[:onmessage] ||= lambda{ |ws,msg,type,info| ws.close } # remember to close ws !
37
+ opts[:onclose] ||= lambda{ |ws,info| }
38
+
39
+ connection_timeout = opts[:connection_timeout] || 1 # timeout until onopen
40
+ message_timeout = opts[:message_timeout] || 5 # timeout between messages
41
+
42
+ timer = EM::Timer.new(connection_timeout){ raise Timeout::Error.new "Waited #{connection_timeout} seconds !" }
43
+ ws = WebSocket::EventMachine::Client.connect(:uri => 'ws://0.0.0.0:3000')
44
+
45
+ ws.onopen do
46
+ timer.cancel
47
+ Fiber.new do opts[:onopen].call(ws,info) end.resume
48
+ timer = EM::Timer.new(message_timeout){ raise Timeout::Error.new "Waited #{message_timeout} seconds !" }
49
+ end
50
+
51
+ ws.onmessage do |msg, type|
52
+ timer.cancel
53
+ info[:messages] ||= []
54
+ info[:messages] << JSON.parse(msg)
55
+ Fiber.new do opts[:onmessage].call(ws,msg,type,info) end.resume
56
+ timer = EM::Timer.new(message_timeout){ raise Timeout::Error.new "Waited #{message_timeout} seconds !" }
57
+ end
58
+
59
+ ws.onclose do
60
+ timer.cancel
61
+ Fiber.new do opts[:onclose].call(ws,info) end.resume
62
+ end
63
+ end
64
+
65
+ def wait_for(timeout = 3)
66
+ loc = caller_locations(1,1)[0]
67
+ place = loc.path + ":" + loc.lineno.to_s
68
+ timer = EM::Timer.new(timeout){ assert false, "Waited #{timeout} seconds on #{place}" }
69
+ while(!yield) do EM::Synchrony.sleep(0.1) end
70
+ timer.cancel
71
+ end
72
+
73
+ def check_is_json(txt)
74
+ begin
75
+ data = JSON.parse(txt)
76
+ rescue => err
77
+ H.log_ex err, msg: "Is not JSON: #{txt}"
78
+ end
79
+ assert !data.nil?, msg: "Is not JSON: #{txt}"
80
+ data
81
+ end
82
+
83
+ # make a call to test controller from within the same client
84
+ # assumes no one is making any calls while it's acting
85
+ #
86
+ def call(ws, action, *args)
87
+ cmd = [WAMP::CALL, 'id', "http://test##{action}"] + args
88
+ result = nil
89
+
90
+ # save current callback
91
+ prev_onmessage = ws.instance_variable_get(:@onmessage)
92
+
93
+ calling_fiber = Fiber.current
94
+
95
+ # put in place a temp callback to get the result
96
+ new_onmessage = Proc.new do |msg, type|
97
+ data = check_is_json msg
98
+ result = data.last
99
+ calling_fiber.resume
100
+ end
101
+ ws.instance_variable_set(:@onmessage, new_onmessage)
102
+
103
+ # make the call
104
+ ws.send cmd.to_json
105
+
106
+ # yield until the callback resumes this fiber
107
+ Fiber.yield
108
+
109
+ # return the original callback
110
+ ws.instance_variable_set(:@onmessage, prev_onmessage)
111
+
112
+ result
113
+ end
114
+
115
+ # make a request to test controller running a new ws_client
116
+ def request(action, *args)
117
+ cmd = [WAMP::CALL, 'id', "http://test##{action}"] + args
118
+ result = nil
119
+ cb = lambda do |ws,msg,type,info|
120
+ data = check_is_json msg
121
+ if data.first == WAMP::WELCOME then
122
+ ws.send cmd.to_json
123
+ else # CALLRESULT or CALLERROR
124
+ result = data.last
125
+ ws.close
126
+ end
127
+ end
128
+ run_ws_client onmessage: cb
129
+ result
130
+ end
131
+
132
+ def assert(boolean, message=nil)
133
+ message = message.call if message.respond_to?(:call)
134
+ message = H.yellow(message.to_s) + H.brown("\n#{caller}")
135
+ super(boolean, message)
136
+ end
137
+
138
+ def todo(msg = nil, opts = {})
139
+ label = caller_locations(1,1)[0].label
140
+ place = File.basename(caller_locations(1,1)[0].path) + ":" + caller_locations(1,1)[0].lineno.to_s
141
+ msg ||= "\n TODO: #{label} (#{place})..."
142
+ opts[:color] ||= :yellow
143
+ opts[:clean] = true
144
+ H.log msg, opts
145
+ skip msg
146
+ end
147
+
148
+ end
149
+
150
+ ##
151
+ # Start a server before running tests and cleanup afterwards
152
+
153
+ SERVER_CMD="ruby test/test_app/main.rb"
154
+
155
+ def is_online?
156
+ s = TCPSocket.new 'localhost', 3000
157
+ s.close
158
+ true
159
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
160
+ false
161
+ end
162
+
163
+ def wait_for_server_to_be_online(timeout = 5)
164
+ print "Waiting for server to be online..."
165
+ t = Time.now
166
+ while not is_online? do
167
+ print '.'
168
+ raise Timeout::Error.new "Waited #{timeout} seconds !" if Time.now - t > timeout
169
+ sleep 0.2
170
+ end
171
+ end
172
+
173
+ def wait_for_server_to_be_offline(timeout = 5)
174
+ print "Ensuring server is offline..."
175
+ t = Time.now
176
+ while is_online? do
177
+ print '.'
178
+ raise Timeout::Error.new "Waited #{timeout} seconds !" if Time.now - t > timeout
179
+ sleep 0.2
180
+ end
181
+ puts 'ok'
182
+ end
183
+
184
+ def clean_test_stuff
185
+ `pkill -f '#{SERVER_CMD}'`
186
+ wait_for_server_to_be_offline
187
+ end
188
+
189
+ Minitest.after_run{ clean_test_stuff }
190
+ trap("INT") { clean_test_stuff; exit 0 }
191
+
192
+ clean_test_stuff # just in case
193
+
194
+ Thread.new do
195
+ begin
196
+ PTY.spawn( "bundle exec " + SERVER_CMD + " 2>&1" ) do |stdin, stdout, pid|
197
+ begin; stdin.each { |line| print line }; rescue Errno::EIO; end
198
+ end
199
+ rescue PTY::ChildExited; puts "The child process exited!"; end
200
+ end
201
+
202
+ wait_for_server_to_be_online
@@ -0,0 +1,28 @@
1
+ require_relative "lib/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "wamp_server"
5
+ s.version = WAMP::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Ruben Caro"]
8
+ s.email = ["ruben.caro@lanuez.org"]
9
+ s.homepage = "https://github.com/rubencaro/wamp_server"
10
+ s.summary = "Simple WAMPv1 compliant server"
11
+ s.description = "WAMPv1 compliant server to be used "+
12
+ "as a skeleton for nice and shining Ruby apps, based on "+
13
+ "WebSocket EventMachine Server."
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- test/*`.split("\n")
17
+ s.require_paths = ["lib"]
18
+ s.license = "GPLv3"
19
+
20
+ s.required_ruby_version = '>= 2.0.0'
21
+
22
+ s.add_dependency 'websocket-eventmachine-server'
23
+
24
+ s.add_development_dependency 'rake'
25
+ s.add_development_dependency 'minitest'
26
+ s.add_development_dependency 'websocket-eventmachine-client'
27
+ s.add_development_dependency 'em-synchrony'
28
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wamp_server
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Ruben Caro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: websocket-eventmachine-server
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: websocket-eventmachine-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: em-synchrony
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: WAMPv1 compliant server to be used as a skeleton for nice and shining
84
+ Ruby apps, based on WebSocket EventMachine Server.
85
+ email:
86
+ - ruben.caro@lanuez.org
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .travis.yml
93
+ - Gemfile
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - lib/version.rb
98
+ - lib/wamp_server.rb
99
+ - test/log_helpers.rb
100
+ - test/main_test.rb
101
+ - test/test_app/helpers.rb
102
+ - test/test_app/main.rb
103
+ - test/test_app/memory.rb
104
+ - test/test_app/test_app.rb
105
+ - test/test_app/test_controller.rb
106
+ - test/test_helper.rb
107
+ - wamp_server.gemspec
108
+ homepage: https://github.com/rubencaro/wamp_server
109
+ licenses:
110
+ - GPLv3
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: 2.0.0
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.0.14
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Simple WAMPv1 compliant server
132
+ test_files:
133
+ - test/log_helpers.rb
134
+ - test/main_test.rb
135
+ - test/test_app/helpers.rb
136
+ - test/test_app/main.rb
137
+ - test/test_app/memory.rb
138
+ - test/test_app/test_app.rb
139
+ - test/test_app/test_controller.rb
140
+ - test/test_helper.rb
141
+ has_rdoc: