em-websocket-server 0.1.2 → 0.13

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,213 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+
3
+ require 'rubygems'
4
+ require 'web_socket'
5
+ require 'json'
6
+ require 'uuid'
7
+ require 'pp'
8
+
9
+ $games = {}
10
+ $waiting = nil
11
+ $status = nil
12
+
13
+ class StatusChannel < EM::Channel
14
+
15
+ def initialize
16
+ super
17
+ @count = 0
18
+ end
19
+
20
+ def increment
21
+ @count += 1
22
+ push @count
23
+ end
24
+
25
+ def decrement
26
+ @count -= 1
27
+ push @count
28
+ end
29
+ end
30
+
31
+ class Game < EM::Channel
32
+
33
+ attr_accessor :id, :player1, :player2, :current, :grid
34
+
35
+ def initialize player1, player2
36
+ super()
37
+ @id = UUID.new
38
+ @player1 = player1
39
+ @player2 = player2
40
+ @grid = Matrix.diagonal(0,0,0)
41
+ end
42
+
43
+ def set_turn p
44
+ @current = p
45
+ @current.turn!
46
+ end
47
+
48
+ def start!
49
+ @current = nil
50
+ @player1.start!
51
+ @player2.start!
52
+ toggle
53
+ end
54
+
55
+ def move p, data
56
+ if @current == p
57
+ unless @matrix[data["x"].to_i][data["y"].to_i]
58
+ @matrix[data["x"].to_i][data["y"].to_i] = p.key
59
+
60
+ @player1.send_move(p.key, data)
61
+ @player2.send_move(p.key, data)
62
+
63
+
64
+ winner = has_winner?
65
+ full = full?
66
+
67
+ if winner || full
68
+ @player1.send_command("game_over")
69
+ @player2.send_command("game_over")
70
+ if winner
71
+ p.send_command("win")
72
+ opponent(p).send_command("loss")
73
+ else full?
74
+ @player1.send_command("draw")
75
+ @player2.send_command("draw")
76
+ end
77
+ else
78
+ toggle
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def full?
85
+ @matrix.each do |row|
86
+ row.each do |col|
87
+ return false unless col
88
+ end
89
+ end
90
+ return true
91
+ end
92
+
93
+ def has_winner?
94
+ return true if @matrix[1][1] && (@matrix[0][0] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][2])
95
+ return true if @matrix[1][1] && (@matrix[0][2] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][0])
96
+ @matrix.each do |row|
97
+ return true if row[1] && (row[0] == row[1]) && (row[1] == row[2])
98
+ end
99
+ return false
100
+ end
101
+
102
+ def opponent p
103
+ @player1 == p ? @player2 : @player1
104
+ end
105
+
106
+ def toggle
107
+ set_turn(@current == @player1 ? @player2 : @player1)
108
+ end
109
+ end
110
+
111
+ class TickTackToeServer < WebSocket::Server
112
+
113
+ attr_accessor :game_id, :key, :status_key
114
+
115
+ def on_connect
116
+ @status_key = $status.subscribe do |c|
117
+ send_user_count c
118
+ end
119
+ $status.increment
120
+ end
121
+
122
+ def on_disconnect
123
+ $status.unsubscribe @status_key
124
+ $status.decrement
125
+ delete_game!
126
+ end
127
+
128
+ def on_receive data
129
+
130
+ begin
131
+ msg = JSON.parse(data)
132
+ rescue
133
+ send_command "error"
134
+ return
135
+ end
136
+
137
+ case msg["msg"]
138
+ when "join"
139
+ if $waiting.empty?
140
+ $waiting.push(self)
141
+ send_command "queued"
142
+ else
143
+ $waiting.pop do |opponent|
144
+ game = Game.new(self, opponent)
145
+ self.key = "X"
146
+ opponent.key = "O"
147
+ self.game_id = opponent.game_id = game.id
148
+ game.start!
149
+ $games[game_id] = game
150
+ end
151
+ end
152
+ when "move"
153
+ if game
154
+ game.move self, msg["data"]
155
+
156
+ else
157
+ log "Cannot move on a nil game!"
158
+ end
159
+ end
160
+ end
161
+
162
+ def delete_game!
163
+ if $games.key?(game_id)
164
+ $games.delete(game_id)
165
+ end
166
+ end
167
+
168
+ def game
169
+ $games[game_id]
170
+ end
171
+
172
+ def turn!
173
+ send_command "turn"
174
+ end
175
+
176
+ def game_over!
177
+ delete_game!
178
+ send_command "game_over"
179
+ end
180
+
181
+ def start!
182
+ send_command "start"
183
+ end
184
+
185
+ def send_move key, data
186
+ send_message({:msg => "move", :key => key, :data => data}.to_json)
187
+ end
188
+
189
+ def send_command cmd
190
+ send_message({:msg => cmd}.to_json)
191
+ end
192
+
193
+ def send_user_count count
194
+ send_message({:msg => :user_count, :data => count}.to_json)
195
+ end
196
+
197
+
198
+ def send_message msg
199
+ super msg
200
+ puts "Sent: #{msg}"
201
+ end
202
+ end
203
+
204
+
205
+ EM.epoll
206
+ EM.set_descriptor_table_size(10240)
207
+
208
+ EM.run do
209
+ $waiting = EM::Queue.new
210
+ $status = StatusChannel.new
211
+
212
+ EM.start_server "0.0.0.0", 8000, TickTackToeServer
213
+ end
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>em-websocket-server test</title>
5
+ </head>
6
+ <body>
7
+
8
+ <h1>em-websocket-server timesync demo</h1>
9
+ <h2 id="time">opening socket</h2>
10
+
11
+ <script>
12
+ var webSocket = new WebSocket('ws://localhost:8000/time');
13
+
14
+ webSocket.onopen = function(event){
15
+ document.getElementById('time').innerHTML = 'waiting for socket';
16
+ };
17
+
18
+ webSocket.onmessage = function(event){
19
+ var object = JSON.parse(event.data);
20
+ document.getElementById('time').innerHTML = object.time;
21
+ };
22
+
23
+ webSocket.onclose = function(event){
24
+ document.getElementById('time').innerHTML = 'socket closed';
25
+ };
26
+ </script>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,37 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'web_socket'
5
+ require 'json'
6
+
7
+ class TimeServer < WebSocket::Server
8
+
9
+ def on_connect
10
+ puts "Connection Accepted"
11
+ @timer = EM.add_periodic_timer(5, EM.Callback(self, :sync_time))
12
+ end
13
+
14
+ def on_disconnect
15
+ puts "Connection released"
16
+ EM.cancel_timer @timer
17
+ end
18
+
19
+ def on_receive msg
20
+ end
21
+
22
+ def sync_time
23
+ puts "Hi"
24
+ send_message({ :time => Time.now }.to_json)
25
+ end
26
+
27
+ end
28
+
29
+
30
+ EM.epoll = true if EM.epoll?
31
+ EM.kqueue = true if EM.kqueue?
32
+
33
+ EM.set_descriptor_table_size(10240)
34
+
35
+ EM.run do
36
+ EM.start_server "0.0.0.0", 8000, TimeServer
37
+ end
data/lib/web_socket.rb CHANGED
@@ -1,11 +1,13 @@
1
- require 'rubygems'
2
- require 'eventmachine'
3
- require 'pp'
4
-
5
- module WebSocket
6
- end
7
-
8
- require 'web_socket/util.rb'
9
- require 'web_socket/frame.rb'
10
- require 'web_socket/server.rb'
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'digest'
4
+ require 'pp'
5
+
6
+ module WebSocket
7
+ VERSION = 0.13
8
+ end
9
+
10
+ require 'web_socket/util.rb'
11
+ require 'web_socket/frame.rb'
12
+ require 'web_socket/server.rb'
11
13
  require 'web_socket/client.rb'
@@ -1,69 +1,71 @@
1
-
2
- module WebSocket
3
-
4
- class Client < EM::Connection
5
-
6
- def path
7
- "/chat"
8
- end
9
-
10
- def host
11
- "localhost:8000"
12
- end
13
-
14
- def origin
15
- "localhost"
16
- end
17
-
18
- # em override
19
- def post_init
20
- @connected = false
21
- end
22
-
23
- def connection_completed
24
- send_headers
25
- end
26
-
27
- # em override
28
- def unbind
29
- on_disconnect
30
- end
31
-
32
- def on_disconnect
33
- end
34
-
35
- def send_message msg
36
- send_data Frame.encode(msg)
37
- end
38
-
39
- private
40
-
41
- def receive_data data
42
- unless @connected
43
- handshake data
44
- else
45
- on_receive Frame.decode(data)
46
- end
47
- end
48
-
49
- def handshake data
50
-
51
- #convert the headers to a hash
52
- @headers = Util.parse_headers(data)
53
- @connected = true
54
-
55
- on_connect
56
- end
57
-
58
- def send_headers
59
- result = "GET #{path} HTTP/1.1\r\n"
60
- result << "Upgrade: WebSocket\r\n"
61
- result << "Connection: Upgrade\r\n"
62
- result << "Host: #{host}\r\n"
63
- result << "Origin: #{origin}\r\n\r\n"
64
-
65
- send_data result
66
- end
67
- end
68
-
1
+
2
+ module WebSocket
3
+
4
+ class Client < EM::Connection
5
+
6
+ def path
7
+ "/chat"
8
+ end
9
+
10
+ def host
11
+ "localhost:8000"
12
+ end
13
+
14
+ def origin
15
+ "localhost"
16
+ end
17
+
18
+ # em override
19
+ def post_init
20
+ @connected = false
21
+ end
22
+
23
+ def connection_completed
24
+ send_headers
25
+ end
26
+
27
+ # em override
28
+ def unbind
29
+ on_disconnect
30
+ end
31
+
32
+ def on_disconnect
33
+ end
34
+
35
+ def send_message msg
36
+ send_data Frame.encode(msg)
37
+ end
38
+
39
+ private
40
+
41
+ def receive_data data
42
+ unless @connected
43
+ handshake data
44
+ else
45
+ while msg = data.slice!(/\000([^\377]*)\377/)
46
+ on_receive Frame.decode(msg)
47
+ end
48
+ end
49
+ end
50
+
51
+ def handshake data
52
+
53
+ #convert the headers to a hash
54
+ @headers = Util.parse_headers(data)
55
+ @connected = true
56
+
57
+ on_connect
58
+ end
59
+
60
+ def send_headers
61
+ result = "GET #{path} HTTP/1.1\r\n"
62
+ result << "Upgrade: WebSocket\r\n"
63
+ result << "Connection: Upgrade\r\n"
64
+ result << "Host: #{host}\r\n"
65
+ result << "Origin: #{origin}\r\n\r\n"
66
+
67
+ send_data result
68
+ end
69
+ end
70
+
69
71
  end
@@ -1,146 +1,202 @@
1
- module WebSocket
2
-
3
- class Server < EM::Connection
4
-
5
- @@logger = nil
6
- @@num_connections = 0
7
- @@callbacks = {}
8
- @@accepted_origins = []
9
-
10
- attr_accessor :connected,
11
- :headers
12
-
13
- def initialize *args
14
- super
15
- @connected = false
16
- end
17
-
18
- def valid_origin?
19
- @@accepted_origins.empty? || @@accepted_origins.include?(origin)
20
- end
21
-
22
- #not doing anything with this yet
23
- def valid_path?
24
- true
25
- end
26
-
27
- def valid_upgrade?
28
- @headers[:upgrade] =~ /websocket/i
29
- end
30
-
31
- def origin
32
- @headers[:origin]
33
- end
34
-
35
- def host
36
- @headers[:host]
37
- end
38
-
39
- def path
40
- @headers[:path]
41
- end
42
-
43
- def cookies
44
- @headers[:cookie]
45
- end
46
-
47
- def self.path name, &block
48
- @@callbacks[name] = block
49
- end
50
-
51
- #tcp connection established
52
- def post_init
53
- @@num_connections += 1
54
- end
55
-
56
- #must be public for em
57
- def unbind
58
- @@num_connections -= 1
59
- on_disconnect
60
- end
61
-
62
- def send_message msg
63
- send_data Frame.encode(msg)
64
- end
65
-
66
- protected
67
-
68
- #override this method
69
- def on_receive msg
70
- log msg
71
- end
72
-
73
- #override this method
74
- def on_connect
75
- log "connected"
76
- end
77
-
78
- #override this method
79
- def on_disconnect
80
- log "disconnected"
81
- end
82
-
83
- def log msg
84
- if @@logger
85
- @@logger.info msg
86
- else
87
- puts msg
88
- end
89
- end
90
-
91
- private
92
-
93
- # when the connection receives data from the client
94
- # we either handshake or handle the message at
95
- # the app layer
96
- def receive_data data
97
- unless @connected
98
- handshake data
99
- else
100
- on_receive Frame.decode(data)
101
- end
102
- end
103
-
104
- # parse the headers, validate the origin and path
105
- # and respond with appropiate headers for a
106
- # healthy relationship with the client
107
- def handshake data
108
-
109
- #convert the headers to a hash
110
- @headers = Util.parse_headers(data)
111
-
112
- # close the connection if the connection
113
- # originates from an invalid source
114
- close_connection unless valid_origin?
115
-
116
- # close the connection if a callback
117
- # is not registered for the path
118
- close_connection unless valid_path?
119
-
120
- # don't respond to non-websocket HTTP requests
121
- close_connection unless valid_upgrade?
122
-
123
- #complete the handshake
124
- send_headers
125
-
126
- @connected = true
127
-
128
- on_connect
129
- end
130
-
131
- # send the handshake response headers to
132
- # complete the initial com
133
- def send_headers
134
-
135
- response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
136
- response << "Upgrade: WebSocket\r\n"
137
- response << "Connection: Upgrade\r\n"
138
- response << "WebSocket-Origin: #{origin}\r\n"
139
- response << "WebSocket-Location: ws://#{host}#{path}\r\n\r\n"
140
-
141
- send_data response
142
- end
143
-
144
- end
145
-
1
+ module WebSocket
2
+
3
+ class Server < EM::Connection
4
+
5
+ @@logger = nil
6
+ @@num_connections = 0
7
+ @@callbacks = {}
8
+ @@accepted_origins = []
9
+
10
+ attr_accessor :connected,
11
+ :headers
12
+
13
+ def initialize *args
14
+ super
15
+ @connected = false
16
+ @protocol_version = 75
17
+ end
18
+
19
+ def valid_origin?
20
+ @@accepted_origins.empty? || @@accepted_origins.include?(origin)
21
+ end
22
+
23
+ #not doing anything with this yet
24
+ def valid_path?
25
+ true
26
+ end
27
+
28
+ def valid_upgrade?
29
+ @headers[:upgrade] =~ /websocket/i
30
+ end
31
+
32
+ def origin
33
+ @headers[:origin]
34
+ end
35
+
36
+ def host
37
+ @headers[:host]
38
+ end
39
+
40
+ def path
41
+ @headers[:path]
42
+ end
43
+
44
+ def cookies
45
+ @headers[:cookie]
46
+ end
47
+
48
+ def protocol
49
+ @headers[:protocol]
50
+ end
51
+
52
+ def self.path name, &block
53
+ @@callbacks[name] = block
54
+ end
55
+
56
+ #tcp connection established
57
+ def post_init
58
+ @@num_connections += 1
59
+ end
60
+
61
+ #must be public for em
62
+ def unbind
63
+ @@num_connections -= 1
64
+ on_disconnect
65
+ end
66
+
67
+ def send_message msg
68
+ send_data Frame.encode(msg)
69
+ end
70
+
71
+ protected
72
+
73
+ #override this method
74
+ def on_receive msg
75
+ log msg
76
+ end
77
+
78
+ #override this method
79
+ def on_connect
80
+ log "connected"
81
+ end
82
+
83
+ #override this method
84
+ def on_disconnect
85
+ log "disconnected"
86
+ end
87
+
88
+ def log msg
89
+ if @@logger
90
+ @@logger.info msg
91
+ else
92
+ puts msg
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # when the connection receives data from the client
99
+ # we either handshake or handle the message at
100
+ # the app layer
101
+ def receive_data data
102
+ unless @connected
103
+ handshake data
104
+ else
105
+ while msg = data.slice!(/\000([^\377]*)\377/)
106
+ on_receive Frame.decode(msg)
107
+ end
108
+ end
109
+ end
110
+
111
+ # parse the headers, validate the origin and path
112
+ # and respond with appropiate headers for a
113
+ # healthy relationship with the client
114
+ def handshake data
115
+ #convert the headers to a hash
116
+ @headers, challenge = Util.parse_headers(data)
117
+
118
+ if challenge
119
+ @protocol_version = 76
120
+
121
+ key1 = @headers[:'sec-websocket-key1']
122
+ key2 = @headers[:'sec-websocket-key2']
123
+
124
+ part1 = number_from_key(key1)
125
+ part2 = number_from_key(key2)
126
+
127
+ unless part1 && part2
128
+ close_connection
129
+ return
130
+ end
131
+
132
+ buffer = []
133
+ buffer += big_endian_bytes(part1)
134
+ buffer += big_endian_bytes(part2)
135
+ buffer += challenge.bytes.to_a
136
+
137
+ md5 = Digest::MD5.new
138
+ hash = md5.digest(buffer.pack('c*'))
139
+ end
140
+
141
+ # close the connection if the connection
142
+ # originates from an invalid source
143
+ close_connection unless valid_origin?
144
+
145
+ # close the connection if a callback
146
+ # is not registered for the path
147
+ close_connection unless valid_path?
148
+
149
+ # don't respond to non-websocket HTTP requests
150
+ close_connection unless valid_upgrade?
151
+
152
+ #complete the handshake
153
+ send_headers(hash)
154
+
155
+ @connected = true
156
+
157
+ on_connect
158
+ end
159
+
160
+ # send the handshake response headers to
161
+ # complete the initial com
162
+ def send_headers(hash)
163
+ response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
164
+ response << "Upgrade: WebSocket\r\n"
165
+ response << "Connection: Upgrade\r\n"
166
+ if @protocol_version > 75
167
+ response << "Sec-WebSocket-Origin: #{origin}\r\n"
168
+ response << "Sec-WebSocket-Location: ws://#{host}#{path}\r\n"
169
+ response << "Sec-WebSocket-protocol: ws://#{host}#{path}\r\n"
170
+ else
171
+ response << "WebSocket-Origin: #{origin}\r\n"
172
+ response << "WebSocket-Location: ws://#{host}#{path}\r\n"
173
+ end
174
+ response << "\r\n"
175
+ response << hash if @protocol_version > 75
176
+
177
+
178
+ send_data response
179
+ end
180
+
181
+ def number_from_key(key)
182
+ digits = key.scan(/\d+/).join.to_i
183
+ spaces = key.scan(/\s+/).join.length
184
+
185
+ if spaces > 0
186
+ return digits / spaces
187
+ else
188
+ return nil
189
+ end
190
+ end
191
+
192
+ def big_endian_bytes(num)
193
+ bytes = []
194
+ 4.times do
195
+ bytes.unshift(num & 0xff)
196
+ num >>= 8
197
+ end
198
+ bytes
199
+ end
200
+ end
201
+
146
202
  end