em-websocket-server 0.1.1 → 0.1.2

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.
data/README.markdown CHANGED
@@ -33,7 +33,7 @@ Not yet... coming soon
33
33
  #create a channel for pub sub
34
34
  $chatroom = EM::Channel.new
35
35
 
36
- class ChatServer < WebSocketServer
36
+ class ChatServer < WebSocket::Server
37
37
 
38
38
  #subscribe to the channel on client connect
39
39
  def on_connect
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'em-websocket-server'
3
- s.version = '0.1.1'
3
+ s.version = '0.1.2'
4
4
  s.date = '2009-12-14'
5
5
  s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
6
  s.email = "dan.simpson@gmail.com"
@@ -16,13 +16,10 @@ spec = Gem::Specification.new do |s|
16
16
  s.files = [
17
17
  "README.markdown",
18
18
  "em-websocket-server.gemspec",
19
- "examples/chat.rb",
20
- "examples/chat.html",
21
- "examples/timesync.html",
22
- "examples/timesync.rb",
23
- "examples/tictactoe/tictactoe.html",
24
- "examples/tictactoe/tictactoe.js",
25
- "examples/tictactoe/tictactoe.rb",
26
- "lib/em-websocket-server.rb"
19
+ "lib/web_socket.rb",
20
+ "lib/web_socket/util.rb",
21
+ "lib/web_socket/frame.rb",
22
+ "lib/web_socket/client.rb",
23
+ "lib/web_socket/server.rb"
27
24
  ]
28
25
  end
data/lib/web_socket.rb ADDED
@@ -0,0 +1,11 @@
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'
11
+ require 'web_socket/client.rb'
@@ -0,0 +1,69 @@
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
+
69
+ end
@@ -0,0 +1,20 @@
1
+ module WebSocket
2
+
3
+ class Frame
4
+
5
+ # Frames need to start with 0x00-0x7f byte and end with
6
+ # an 0xFF byte. Per spec, we can also set the first
7
+ # byte to a value betweent 0x80 and 0xFF, followed by
8
+ # a leading length indicator. No support yet.
9
+ def self.encode data
10
+ "\x00#{data}\xff"
11
+ end
12
+
13
+ # Strip leading and trailing bytes
14
+ def self.decode data
15
+ data.gsub(/^(\x00)|(\xff)$/, "")
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,146 @@
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
+
146
+ end
@@ -0,0 +1,50 @@
1
+ module WebSocket
2
+
3
+ class Util
4
+
5
+ @@path_regex = /^GET (\/[^\s]*) HTTP\/1\.1$/
6
+ @@hs_regex = /^HTTP\/1\.1 101 Web Socket Protocol Handshake$/
7
+ @@header_regex = /^([^:]+):\s*([^$]+)/
8
+
9
+ # Parse http style headers into a ruby hash
10
+ def self.parse_headers data
11
+ lines = data.split("\r\n")
12
+ line = lines.shift
13
+
14
+ headers = {}
15
+
16
+ case line
17
+ when @@path_regex
18
+ headers[:path] = @@path_regex.match(line)[1]
19
+ when @@hs_regex
20
+ #do nothing
21
+ else
22
+ throw "Unrecognized Header!"
23
+ end
24
+
25
+
26
+ lines.each do |line|
27
+ kvp = @@header_regex.match(line)
28
+ headers[kvp[1].strip.downcase.to_sym] = kvp[2].strip
29
+ end
30
+
31
+ headers
32
+ end
33
+
34
+ # encode ruby hash into HTTP style headers
35
+ def self.encode_headers data
36
+ result = ""
37
+
38
+ data.each_pair do |k,v|
39
+ result << k.to_s
40
+ result << ": "
41
+ result << v.to_s
42
+ result << "\r\n"
43
+ end
44
+
45
+ result << "\r\n"
46
+ result
47
+ end
48
+
49
+ end
50
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-websocket-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Simpson
@@ -33,14 +33,11 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - README.markdown
35
35
  - em-websocket-server.gemspec
36
- - examples/chat.rb
37
- - examples/chat.html
38
- - examples/timesync.html
39
- - examples/timesync.rb
40
- - examples/tictactoe/tictactoe.html
41
- - examples/tictactoe/tictactoe.js
42
- - examples/tictactoe/tictactoe.rb
43
- - lib/em-websocket-server.rb
36
+ - lib/web_socket.rb
37
+ - lib/web_socket/util.rb
38
+ - lib/web_socket/frame.rb
39
+ - lib/web_socket/client.rb
40
+ - lib/web_socket/server.rb
44
41
  has_rdoc: true
45
42
  homepage: http://github.com/dansimpson/em-websocket-server
46
43
  licenses: []
data/examples/chat.html DELETED
@@ -1,40 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <title>em-websocket-server test</title>
5
- </head>
6
- <body>
7
-
8
-
9
- <h1>em-websocket-server chat demo</h1>
10
- <div id="chat">connecting....</div>
11
-
12
-
13
-
14
- <script>
15
-
16
- var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
17
-
18
- webSocket.onopen = function(event){
19
- document.getElementById('chat').innerHTML = 'connected';
20
- };
21
-
22
- webSocket.onmessage = function(event){
23
- var object = JSON.parse(event.data);
24
- document.getElementById('chat').innerHTML += "<b>" + object.user + "</b>" + object.message;
25
- };
26
-
27
- webSocket.onclose = function(event){
28
- document.getElementById('chat').innerHTML = 'socket closed';
29
- };
30
-
31
- document.getElementById('chatty').addEventListener("onkeypress", function() {
32
- alert("X")
33
- });
34
-
35
- </script>
36
-
37
- <input type="text" onkeypress="webSocket.send('hi');" />
38
-
39
- </body>
40
- </html>
data/examples/chat.rb DELETED
@@ -1,33 +0,0 @@
1
- $:.unshift File.dirname(__FILE__) + '/../lib'
2
-
3
- require 'rubygems'
4
- require 'em-websocket-server'
5
- require 'json'
6
-
7
- $chatroom = EM::Channel.new
8
-
9
- class ChatServer < WebSocketServer
10
-
11
- def on_connect
12
- @sid = $chatroom.subscribe do |msg|
13
- send_message msg
14
- end
15
- end
16
-
17
- def on_disconnect
18
- $chatroom.unsubscribe(@sid)
19
- end
20
-
21
- def on_receive msg
22
- $chatroom.push msg
23
- end
24
-
25
- end
26
-
27
-
28
- EM.epoll
29
- EM.set_descriptor_table_size(10240)
30
-
31
- EM.run do
32
- EM.start_server "0.0.0.0", 8000, ChatServer
33
- end
@@ -1,51 +0,0 @@
1
- <!doctype html>
2
-
3
- <html>
4
- <head>
5
- <title>Tic Tac Toe</title>
6
- <meta name="author" content="Jordan Fowler (TheBreeze)">
7
-
8
- <style type="text/css" media="screen">
9
- #navigation ul {
10
- list-style-type: none;
11
- padding: 0;
12
- margin: 25px 0px;
13
- }
14
-
15
- #navigation ul li {
16
- display: inline;
17
- padding: 0;
18
- margin: 0;
19
- margin-right: 10px;
20
- }
21
-
22
- #game-board .cell {
23
- width: 100px;
24
- height: 100px;
25
- padding: 5px;
26
- background: #000;
27
- color: #FFF;
28
- font-size: 100px;
29
- text-align: center;
30
- vertical-align: middle;
31
- }
32
- </style>
33
- </head>
34
- <body>
35
-
36
- <h1>Tic Tac Toe</h1>
37
-
38
- <div id="user-count"></div>
39
- <div id="game-stats"></div>
40
- <div id="status"></div>
41
-
42
- <table id="game-board">
43
- <tr><td id="cell-0-0" class="cell"></td><td id="cell-1-0" class="cell"></td><td id="cell-2-0" class="cell"></td></tr>
44
- <tr><td id="cell-0-1" class="cell"></td><td id="cell-1-1" class="cell"></td><td id="cell-2-1" class="cell"></td></tr>
45
- <tr><td id="cell-0-2" class="cell"></td><td id="cell-1-2" class="cell"></td><td id="cell-2-2" class="cell"></td></tr>
46
- </table>
47
-
48
- <script type="text/javascript" charset="utf-8" src="mootools-1.2.4-core-nc.js"></script>
49
- <script type="text/javascript" charset="utf-8" src="tictactoe.js"></script>
50
- </body>
51
- </html>
@@ -1,130 +0,0 @@
1
- // Author: Jordan Fowler (TheBreeze)
2
-
3
- var TicTacToe = new Class({
4
- wins: 0,
5
- loses: 0,
6
- draws: 0,
7
- methodMap: {
8
- 'queued': 'onQueued',
9
- 'start': 'onStart',
10
- 'turn': 'onTurn',
11
- 'move': 'onMove',
12
- 'game_over': 'onGameOver',
13
- 'win': 'onWin',
14
- 'loss': 'onLoss',
15
- 'draw': 'onDraw',
16
- 'user_count': 'onUserCount'
17
- },
18
-
19
- initialize: function() {
20
- if (TicTacToe.connection == null) {
21
- TicTacToe.connection = new WebSocket('ws://192.168.0.2:8000/tictactoe');
22
- };
23
-
24
- TicTacToe.connection.onopen = this.join.bind(this);
25
- TicTacToe.connection.onmessage = this.onMessage.bind(this);
26
- TicTacToe.connection.onclose = this.onGameOver.bind(this);
27
-
28
- this.setGameStats();
29
- },
30
-
31
- onMessage: function(event) {
32
- var command = JSON.decode(event.data);
33
-
34
- console.log('[RCV] ' + command.msg);
35
-
36
- this[this.methodMap[command.msg]].call(this, command);
37
- },
38
-
39
- message: function(msg, options) {
40
- var command = JSON.encode({msg: msg, data: options});
41
-
42
- console.log('[SENT] ' + msg);
43
-
44
- TicTacToe.connection.send(command);
45
- },
46
-
47
- setStatus: function(status) {
48
- $('status').set('text', status);
49
- },
50
-
51
- setGameStats: function() {
52
- $('game-stats').set('text', 'Wins: '+this.wins+' / Losses: '+this.wins+' / Draws: '+this.draws);
53
- },
54
-
55
- setUserCount: function(userCount) {
56
- $('user-count').set('text', 'Number of players: ' + userCount);
57
- },
58
-
59
- join: function(event) {
60
- this.message('join');
61
-
62
- this.setStatus('Connecting you to a game...');
63
- },
64
-
65
- reset: function() {
66
- $$('.cell').set('text', '');
67
-
68
- this.join();
69
- },
70
-
71
- onQueued: function(command) {
72
- this.setStatus('Waiting for another player...');
73
- },
74
-
75
- onStart: function(command) {
76
- this.setStatus('Game found! Their turn first...');
77
- },
78
-
79
- onTurn: function(command) {
80
- this.setStatus('Your turn...');
81
- },
82
-
83
- onMove: function(command) {
84
- $('cell-'+command.data.x+'-'+command.data.y).set('text', command.key);
85
-
86
- this.setStatus('Their turn...');
87
- },
88
-
89
- move: function(x, y) {
90
- this.message('move', {x: x, y: y});
91
- },
92
-
93
- onGameOver: function(command) {
94
- this.setStatus('Game over.');
95
- this.setGameStats();
96
- this.reset();
97
- },
98
-
99
- onWin: function(command) {
100
- this.wins += 1;
101
- this.setStatus('Game over. You win!');
102
- this.setGameStats();
103
- },
104
-
105
- onLoss: function(command) {
106
- this.losses += 1;
107
- this.setStatus('Game over. You lose!');
108
- this.setGameStats();
109
- },
110
-
111
- onDraw: function(command) {
112
- this.draws += 1;
113
- this.setStatus('Game over. It was a draw!');
114
- this.setGameStats();
115
- },
116
-
117
- onUserCount: function(command) {
118
- this.setUserCount(command.data);
119
- }
120
- });
121
-
122
- $$('.cell').addEvent('click', function(event) {
123
- try {
124
- currentGame.move($(this).get('id').split('-')[1], $(this).get('id').split('-')[2]);
125
- } catch(error) {
126
- alert('Please wait while we connect you to a game...');
127
- }
128
- });
129
-
130
- currentGame = new TicTacToe();
@@ -1,215 +0,0 @@
1
- $:.unshift File.dirname(__FILE__) + '/../../lib'
2
-
3
- require 'rubygems'
4
- require 'em-websocket-server'
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, :matrix
34
-
35
-
36
-
37
- def initialize player1, player2
38
- super()
39
- @id = UUID.new
40
- @player1 = player1
41
- @player2 = player2
42
- @matrix = (0..2).collect { [false, false, false] }
43
- end
44
-
45
- def set_turn p
46
- @current = p
47
- @current.turn!
48
- end
49
-
50
- def start!
51
- @current = nil
52
- @player1.start!
53
- @player2.start!
54
- toggle
55
- end
56
-
57
- def move p, data
58
- if @current == p
59
- unless @matrix[data["x"].to_i][data["y"].to_i]
60
- @matrix[data["x"].to_i][data["y"].to_i] = p.key
61
-
62
- @player1.send_move(p.key, data)
63
- @player2.send_move(p.key, data)
64
-
65
-
66
- winner = has_winner?
67
- full = full?
68
-
69
- if winner || full
70
- @player1.send_command("game_over")
71
- @player2.send_command("game_over")
72
- if winner
73
- p.send_command("win")
74
- opponent(p).send_command("loss")
75
- else full?
76
- @player1.send_command("draw")
77
- @player2.send_command("draw")
78
- end
79
- else
80
- toggle
81
- end
82
- end
83
- end
84
- end
85
-
86
- def full?
87
- @matrix.each do |row|
88
- row.each do |col|
89
- return false unless col
90
- end
91
- end
92
- return true
93
- end
94
-
95
- def has_winner?
96
- return true if @matrix[1][1] && (@matrix[0][0] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][2])
97
- return true if @matrix[1][1] && (@matrix[0][2] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][0])
98
- @matrix.each do |row|
99
- return true if row[1] && (row[0] == row[1]) && (row[1] == row[2])
100
- end
101
- return false
102
- end
103
-
104
- def opponent p
105
- @player1 == p ? @player2 : @player1
106
- end
107
-
108
- def toggle
109
- set_turn(@current == @player1 ? @player2 : @player1)
110
- end
111
- end
112
-
113
- class TickTackToeServer < WebSocketServer
114
-
115
- attr_accessor :game_id, :key, :status_key
116
-
117
- def on_connect
118
- @status_key = $status.subscribe do |c|
119
- send_user_count c
120
- end
121
- $status.increment
122
- end
123
-
124
- def on_disconnect
125
- $status.unsubscribe @status_key
126
- $status.decrement
127
- delete_game!
128
- end
129
-
130
- def on_receive data
131
-
132
- begin
133
- msg = JSON.parse(data)
134
- rescue
135
- send_command "error"
136
- return
137
- end
138
-
139
- case msg["msg"]
140
- when "join"
141
- if $waiting.empty?
142
- $waiting.push(self)
143
- send_command "queued"
144
- else
145
- $waiting.pop do |opponent|
146
- game = Game.new(self, opponent)
147
- self.key = "X"
148
- opponent.key = "O"
149
- self.game_id = opponent.game_id = game.id
150
- game.start!
151
- $games[game_id] = game
152
- end
153
- end
154
- when "move"
155
- if game
156
- game.move self, msg["data"]
157
-
158
- else
159
- log "Cannot move on a nil game!"
160
- end
161
- end
162
- end
163
-
164
- def delete_game!
165
- if $games.key?(game_id)
166
- $games.delete(game_id)
167
- end
168
- end
169
-
170
- def game
171
- $games[game_id]
172
- end
173
-
174
- def turn!
175
- send_command "turn"
176
- end
177
-
178
- def game_over!
179
- delete_game!
180
- send_command "game_over"
181
- end
182
-
183
- def start!
184
- send_command "start"
185
- end
186
-
187
- def send_move key, data
188
- send_message({:msg => "move", :key => key, :data => data}.to_json)
189
- end
190
-
191
- def send_command cmd
192
- send_message({:msg => cmd}.to_json)
193
- end
194
-
195
- def send_user_count count
196
- send_message({:msg => :user_count, :data => count}.to_json)
197
- end
198
-
199
-
200
- def send_message msg
201
- super msg
202
- puts "Sent: #{msg}"
203
- end
204
- end
205
-
206
-
207
- EM.epoll
208
- EM.set_descriptor_table_size(10240)
209
-
210
- EM.run do
211
- $waiting = EM::Queue.new
212
- $status = StatusChannel.new
213
-
214
- EM.start_server "0.0.0.0", 8000, TickTackToeServer
215
- end
@@ -1,29 +0,0 @@
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://192.168.0.2: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>
data/examples/timesync.rb DELETED
@@ -1,35 +0,0 @@
1
- $:.unshift File.dirname(__FILE__) + '/../lib'
2
-
3
- require 'rubygems'
4
- require 'em-websocket-server'
5
- require 'json'
6
-
7
- class TimeServer < WebSocketServer
8
-
9
- def on_connect
10
- @timer = EM.add_periodic_timer(5) do
11
- sync_time
12
- end
13
- end
14
-
15
- def on_disconnect
16
- @timer
17
- end
18
-
19
- def on_receive msg
20
- puts "msg rcv"
21
- end
22
-
23
- def sync_time
24
- send_message({ :time => Time.now }.to_json)
25
- end
26
-
27
- end
28
-
29
-
30
- EM.epoll
31
- EM.set_descriptor_table_size(10240)
32
-
33
- EM.run do
34
- EM.start_server "0.0.0.0", 8000, TimeServer
35
- end
@@ -1,158 +0,0 @@
1
- require 'rubygems'
2
- require 'eventmachine'
3
-
4
- class WebSocketServer < EM::Connection
5
-
6
- @@logger = nil
7
- @@num_connections = 0
8
- @@path_regex = /^GET (\/[^\s]*) HTTP\/1\.1$/
9
- @@header_regex = /^([^:]+):\s*([^$]+)/
10
- @@callbacks = {}
11
- @@accepted_origins = []
12
-
13
- attr_accessor :connected,
14
- :headers,
15
- :path
16
-
17
- def initialize *args
18
- super
19
- @connected = false
20
- end
21
-
22
- def valid_origin?
23
- @@accepted_origins.empty? || @@accepted_origins.include?(origin)
24
- end
25
-
26
- #not doing anything with this yet
27
- def valid_path?
28
- true
29
- end
30
-
31
- def valid_upgrade?
32
- @headers["Upgrade"] =~ /websocket/i
33
- end
34
-
35
- def origin
36
- @headers["Origin"]
37
- end
38
-
39
- def host
40
- @headers["Host"]
41
- end
42
-
43
- def self.path name, &block
44
- @@callbacks[name] = block
45
- end
46
-
47
- #tcp connection established
48
- def post_init
49
- @@num_connections += 1
50
- end
51
-
52
- #must be public for em
53
- def unbind
54
- @@num_connections -= 1
55
- on_disconnect
56
- end
57
-
58
- # Frames need to start with 0x00-0x7f byte and end with
59
- # an 0xFF byte. Per spec, we can also set the first
60
- # byte to a value betweent 0x80 and 0xFF, followed by
61
- # a leading length indicator. No support yet
62
- def send_message msg
63
- send_data "\x00#{msg}\xff"
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, accept the start command
95
- # or handle the message at the app layer
96
- def receive_data data
97
- unless @connected
98
- handshake data
99
- else
100
- on_receive data.gsub(/^(\x00)|(\xff)$/, "")
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
- 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
- # turn http style headers into a ruby hash
145
- # TODO: this is probably not done "well"
146
- def parse_headers data
147
- lines = data.split("\r\n")
148
-
149
- @path = @@path_regex.match(lines.shift)[1]
150
- @headers = {}
151
-
152
- lines.each do |line|
153
- kvp = @@header_regex.match(line)
154
- @headers[kvp[1].strip] = kvp[2].strip
155
- end
156
- end
157
-
158
- end