wamp_server 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: