em-websocket-server 0.1.1 → 0.1.2

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