em-websocket-server 0.1.2 → 0.13

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