em-websocket-server 0.13 → 0.15
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 +98 -38
- data/em-websocket-server.gemspec +17 -27
- data/lib/em-websocket-server.rb +15 -0
- data/lib/em-websocket-server/protocol/version76.rb +49 -0
- data/lib/em-websocket-server/request.rb +109 -0
- data/lib/em-websocket-server/server.rb +157 -0
- metadata +9 -21
- data/examples/chat.html +0 -37
- data/examples/chat.rb +0 -70
- data/examples/tictactoe/tictactoe.html +0 -51
- data/examples/tictactoe/tictactoe.js +0 -130
- data/examples/tictactoe/tictactoe.rb +0 -213
- data/examples/timesync.html +0 -29
- data/examples/timesync.rb +0 -37
- data/lib/web_socket.rb +0 -13
- data/lib/web_socket/client.rb +0 -71
- data/lib/web_socket/frame.rb +0 -20
- data/lib/web_socket/server.rb +0 -202
- data/lib/web_socket/util.rb +0 -55
data/examples/timesync.html
DELETED
@@ -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://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>
|
data/examples/timesync.rb
DELETED
@@ -1,37 +0,0 @@
|
|
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
DELETED
@@ -1,13 +0,0 @@
|
|
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'
|
13
|
-
require 'web_socket/client.rb'
|
data/lib/web_socket/client.rb
DELETED
@@ -1,71 +0,0 @@
|
|
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
|
-
|
71
|
-
end
|
data/lib/web_socket/frame.rb
DELETED
@@ -1,20 +0,0 @@
|
|
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
|
data/lib/web_socket/server.rb
DELETED
@@ -1,202 +0,0 @@
|
|
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
|
-
|
202
|
-
end
|
data/lib/web_socket/util.rb
DELETED
@@ -1,55 +0,0 @@
|
|
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
|
-
while line = lines.shift
|
26
|
-
if line.chomp.empty?
|
27
|
-
break
|
28
|
-
end
|
29
|
-
|
30
|
-
kvp = @@header_regex.match(line)
|
31
|
-
headers[kvp[1].strip.downcase.to_sym] = kvp[2].strip
|
32
|
-
end
|
33
|
-
|
34
|
-
challenge = lines.pop if lines
|
35
|
-
|
36
|
-
[headers, challenge]
|
37
|
-
end
|
38
|
-
|
39
|
-
# encode ruby hash into HTTP style headers
|
40
|
-
def self.encode_headers data
|
41
|
-
result = ""
|
42
|
-
|
43
|
-
data.each_pair do |k,v|
|
44
|
-
result << k.to_s
|
45
|
-
result << ": "
|
46
|
-
result << v.to_s
|
47
|
-
result << "\r\n"
|
48
|
-
end
|
49
|
-
|
50
|
-
result << "\r\n"
|
51
|
-
result
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|