sonixlabs-em-websocket 0.3.7
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/.gitignore +4 -0
- data/CHANGELOG.rdoc +80 -0
- data/Gemfile +3 -0
- data/README.md +98 -0
- data/Rakefile +11 -0
- data/em-websocket.gemspec +27 -0
- data/examples/echo.rb +8 -0
- data/examples/flash_policy_file_server.rb +21 -0
- data/examples/js/FABridge.js +604 -0
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +4 -0
- data/examples/js/web_socket.js +312 -0
- data/examples/multicast.rb +47 -0
- data/examples/test.html +30 -0
- data/lib/em-websocket/client_connection.rb +19 -0
- data/lib/em-websocket/close03.rb +11 -0
- data/lib/em-websocket/close05.rb +11 -0
- data/lib/em-websocket/close06.rb +16 -0
- data/lib/em-websocket/close75.rb +10 -0
- data/lib/em-websocket/connection.rb +184 -0
- data/lib/em-websocket/debugger.rb +17 -0
- data/lib/em-websocket/framing03.rb +167 -0
- data/lib/em-websocket/framing04.rb +15 -0
- data/lib/em-websocket/framing05.rb +168 -0
- data/lib/em-websocket/framing07.rb +180 -0
- data/lib/em-websocket/framing76.rb +114 -0
- data/lib/em-websocket/handler.rb +56 -0
- data/lib/em-websocket/handler03.rb +10 -0
- data/lib/em-websocket/handler05.rb +10 -0
- data/lib/em-websocket/handler06.rb +10 -0
- data/lib/em-websocket/handler07.rb +10 -0
- data/lib/em-websocket/handler08.rb +10 -0
- data/lib/em-websocket/handler13.rb +10 -0
- data/lib/em-websocket/handler75.rb +9 -0
- data/lib/em-websocket/handler76.rb +12 -0
- data/lib/em-websocket/handler_factory.rb +107 -0
- data/lib/em-websocket/handshake04.rb +75 -0
- data/lib/em-websocket/handshake75.rb +21 -0
- data/lib/em-websocket/handshake76.rb +71 -0
- data/lib/em-websocket/masking04.rb +63 -0
- data/lib/em-websocket/message_processor_03.rb +38 -0
- data/lib/em-websocket/message_processor_06.rb +52 -0
- data/lib/em-websocket/version.rb +5 -0
- data/lib/em-websocket/websocket.rb +45 -0
- data/lib/em-websocket.rb +23 -0
- data/lib/sonixlabs-em-websocket.rb +1 -0
- data/spec/helper.rb +146 -0
- data/spec/integration/client_examples.rb +48 -0
- data/spec/integration/common_spec.rb +118 -0
- data/spec/integration/draft03_spec.rb +270 -0
- data/spec/integration/draft05_spec.rb +48 -0
- data/spec/integration/draft06_spec.rb +88 -0
- data/spec/integration/draft13_spec.rb +75 -0
- data/spec/integration/draft75_spec.rb +117 -0
- data/spec/integration/draft76_spec.rb +230 -0
- data/spec/integration/shared_examples.rb +91 -0
- data/spec/unit/framing_spec.rb +325 -0
- data/spec/unit/handler_spec.rb +147 -0
- data/spec/unit/masking_spec.rb +27 -0
- data/spec/unit/message_processor_spec.rb +36 -0
- metadata +198 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
class HandlerFactory
|
4
|
+
PATH = /^(\w+) (\/[^\s]*) HTTP\/1\.1$/
|
5
|
+
HEADER = /^([^:]+):\s*(.+)$/
|
6
|
+
|
7
|
+
def self.build(connection, data, secure = false, debug = false)
|
8
|
+
(header, remains) = data.split("\r\n\r\n", 2)
|
9
|
+
unless remains
|
10
|
+
# The whole header has not been received yet.
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
|
14
|
+
request = {}
|
15
|
+
|
16
|
+
lines = header.split("\r\n")
|
17
|
+
|
18
|
+
# extract request path
|
19
|
+
first_line = lines.shift.match(PATH)
|
20
|
+
raise HandshakeError, "Invalid HTTP header" unless first_line
|
21
|
+
request['method'] = first_line[1].strip
|
22
|
+
request['path'] = first_line[2].strip
|
23
|
+
|
24
|
+
unless request["method"] == "GET"
|
25
|
+
raise HandshakeError, "Must be GET request"
|
26
|
+
end
|
27
|
+
|
28
|
+
# extract query string values
|
29
|
+
request['query'] = Addressable::URI.parse(request['path']).query_values ||= {}
|
30
|
+
# extract remaining headers
|
31
|
+
lines.each do |line|
|
32
|
+
h = HEADER.match(line)
|
33
|
+
request[h[1].strip.downcase] = h[2].strip if h
|
34
|
+
end
|
35
|
+
|
36
|
+
build_with_request(connection, request, remains, secure, debug)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.build_with_request(connection, request, remains, secure = false, debug = false)
|
40
|
+
# Determine version heuristically
|
41
|
+
version = if request['sec-websocket-version']
|
42
|
+
# Used from drafts 04 onwards
|
43
|
+
request['sec-websocket-version'].to_i
|
44
|
+
elsif request['sec-websocket-draft']
|
45
|
+
# Used in drafts 01 - 03
|
46
|
+
request['sec-websocket-draft'].to_i
|
47
|
+
elsif request['sec-websocket-key1']
|
48
|
+
76
|
49
|
+
else
|
50
|
+
75
|
51
|
+
end
|
52
|
+
|
53
|
+
# Additional handling of bytes after the header if required
|
54
|
+
case version
|
55
|
+
when 75
|
56
|
+
if !remains.empty?
|
57
|
+
raise HandshakeError, "Extra bytes after header"
|
58
|
+
end
|
59
|
+
when 76, 1..3
|
60
|
+
if remains.length < 8
|
61
|
+
# The whole third-key has not been received yet.
|
62
|
+
return nil
|
63
|
+
elsif remains.length > 8
|
64
|
+
raise HandshakeError, "Extra bytes after third key"
|
65
|
+
end
|
66
|
+
request['third-key'] = remains
|
67
|
+
end
|
68
|
+
|
69
|
+
# Validate that Connection and Upgrade headers
|
70
|
+
unless request['connection'] && request['connection'] =~ /Upgrade/ && request['upgrade'] && request['upgrade'].downcase == 'websocket'
|
71
|
+
raise HandshakeError, "Connection and Upgrade headers required"
|
72
|
+
end
|
73
|
+
|
74
|
+
# transform headers
|
75
|
+
protocol = (secure ? "wss" : "ws")
|
76
|
+
request['host'] = Addressable::URI.parse("#{protocol}://"+request['host'])
|
77
|
+
|
78
|
+
case version
|
79
|
+
when 75
|
80
|
+
Handler75.new(connection, request, debug)
|
81
|
+
when 76
|
82
|
+
Handler76.new(connection, request, debug)
|
83
|
+
when 1..3
|
84
|
+
# We'll use handler03 - I believe they're all compatible
|
85
|
+
Handler03.new(connection, request, debug)
|
86
|
+
when 5
|
87
|
+
Handler05.new(connection, request, debug)
|
88
|
+
when 6
|
89
|
+
Handler06.new(connection, request, debug)
|
90
|
+
when 7
|
91
|
+
Handler07.new(connection, request, debug)
|
92
|
+
when 8
|
93
|
+
# drafts 9, 10, 11 and 12 should never change the version
|
94
|
+
# number as they are all the same as version 08.
|
95
|
+
Handler08.new(connection, request, debug)
|
96
|
+
when 13
|
97
|
+
# drafts 13 to 17 all identify as version 13 as they are
|
98
|
+
# only minor changes or text changes.
|
99
|
+
Handler13.new(connection, request, debug)
|
100
|
+
else
|
101
|
+
# According to spec should abort the connection
|
102
|
+
raise WebSocketError, "Protocol version #{version} not supported"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
module WebSocket
|
6
|
+
module Handshake04
|
7
|
+
|
8
|
+
def handshake_key_response(key)
|
9
|
+
string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
10
|
+
Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
|
11
|
+
end
|
12
|
+
|
13
|
+
def handshake_server
|
14
|
+
# Required
|
15
|
+
unless key = request['sec-websocket-key']
|
16
|
+
raise HandshakeError, "Sec-WebSocket-Key header is required"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Optional
|
20
|
+
origin = request['sec-websocket-origin']
|
21
|
+
protocols = request['sec-websocket-protocol']
|
22
|
+
extensions = request['sec-websocket-extensions']
|
23
|
+
|
24
|
+
upgrade = ["HTTP/1.1 101 Switching Protocols"]
|
25
|
+
upgrade << "Upgrade: websocket"
|
26
|
+
upgrade << "Connection: Upgrade"
|
27
|
+
upgrade << "Sec-WebSocket-Accept: #{handshake_key_response(key)}"
|
28
|
+
|
29
|
+
# TODO: Support Sec-WebSocket-Protocol
|
30
|
+
# TODO: Sec-WebSocket-Extensions
|
31
|
+
|
32
|
+
[:upgrade_headers, upgrade]
|
33
|
+
|
34
|
+
return upgrade.join("\r\n") + "\r\n\r\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
def handshake_client
|
38
|
+
request = ["GET /websocket HTTP/1.1"]
|
39
|
+
request << "Host: #{@request[:host]}:#{@request[:port]}"
|
40
|
+
request << "Connection: keep-alive, Upgrade"
|
41
|
+
request << "Sec-WebSocket-Version: 8" # TODO: supply version somehow
|
42
|
+
request << "Sec-WebSocket-Origin: null"
|
43
|
+
random16 = (0...16).map{rand(255).chr}.join
|
44
|
+
random16_base64 = Base64.encode64(random16).chomp
|
45
|
+
@correct_response = handshake_key_response random16_base64
|
46
|
+
request << "Sec-WebSocket-Key: #{random16_base64}"
|
47
|
+
request << "Upgrade: websocket"
|
48
|
+
# TODO: anything else needed? nothing else parsed anyway
|
49
|
+
return request.join("\r\n") + "\r\n\r\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def client_handle_server_handshake_response(data)
|
53
|
+
header, msg = data.split "\r\n\r\n"
|
54
|
+
lines = header.split("\r\n")
|
55
|
+
accept = false
|
56
|
+
lines.each do |line|
|
57
|
+
h = /^([^:]+):\s*(.+)$/.match(line)
|
58
|
+
if !h.nil? and h[1].strip.downcase == "sec-websocket-accept"
|
59
|
+
accept = (h[2] == @correct_response)
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if accept
|
64
|
+
@state = :connected #TODO - some actual logic would be nice
|
65
|
+
@connection.trigger_on_open
|
66
|
+
if msg # handle message bundled in with handshake response
|
67
|
+
receive_data(msg)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
close_websocket(1002,nil)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
module Handshake75
|
4
|
+
def handshake
|
5
|
+
location = "#{request['host'].scheme}://#{request['host'].host}"
|
6
|
+
location << ":#{request['host'].port}" if request['host'].port
|
7
|
+
location << request['path']
|
8
|
+
|
9
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
10
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
11
|
+
upgrade << "Connection: Upgrade\r\n"
|
12
|
+
upgrade << "WebSocket-Origin: #{request['origin']}\r\n"
|
13
|
+
upgrade << "WebSocket-Location: #{location}\r\n\r\n"
|
14
|
+
|
15
|
+
debug [:upgrade_headers, upgrade]
|
16
|
+
|
17
|
+
return upgrade
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module Handshake76
|
6
|
+
def handshake
|
7
|
+
challenge_response = solve_challenge(
|
8
|
+
request['sec-websocket-key1'],
|
9
|
+
request['sec-websocket-key2'],
|
10
|
+
request['third-key']
|
11
|
+
)
|
12
|
+
|
13
|
+
location = "#{request['host'].scheme}://#{request['host'].host}"
|
14
|
+
location << ":#{request['host'].port}" if request['host'].port
|
15
|
+
location << request['path']
|
16
|
+
|
17
|
+
upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
18
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
19
|
+
upgrade << "Connection: Upgrade\r\n"
|
20
|
+
upgrade << "Sec-WebSocket-Location: #{location}\r\n"
|
21
|
+
upgrade << "Sec-WebSocket-Origin: #{request['origin']}\r\n"
|
22
|
+
if protocol = request['sec-websocket-protocol']
|
23
|
+
validate_protocol!(protocol)
|
24
|
+
upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
|
25
|
+
end
|
26
|
+
upgrade << "\r\n"
|
27
|
+
upgrade << challenge_response
|
28
|
+
|
29
|
+
debug [:upgrade_headers, upgrade]
|
30
|
+
|
31
|
+
return upgrade
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def solve_challenge(first, second, third)
|
37
|
+
# Refer to 5.2 4-9 of the draft 76
|
38
|
+
sum = [numbers_over_spaces(first)].pack("N*") +
|
39
|
+
[numbers_over_spaces(second)].pack("N*") +
|
40
|
+
third
|
41
|
+
Digest::MD5.digest(sum)
|
42
|
+
end
|
43
|
+
|
44
|
+
def numbers_over_spaces(string)
|
45
|
+
numbers = string.scan(/[0-9]/).join.to_i
|
46
|
+
|
47
|
+
spaces = string.scan(/ /).size
|
48
|
+
# As per 5.2.5, abort the connection if spaces are zero.
|
49
|
+
raise HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0
|
50
|
+
|
51
|
+
# As per 5.2.6, abort if numbers is not an integral multiple of spaces
|
52
|
+
if numbers % spaces != 0
|
53
|
+
raise HandshakeError, "Invalid Key #{string.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
quotient = numbers / spaces
|
57
|
+
|
58
|
+
if quotient > 2**32-1
|
59
|
+
raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
return quotient
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_protocol!(protocol)
|
66
|
+
raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
|
67
|
+
# TODO: Validate characters
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
class MaskedString < String
|
4
|
+
# Read a 4 bit XOR mask - further requested bytes will be unmasked
|
5
|
+
def read_mask
|
6
|
+
if respond_to?(:encoding) && encoding.name != "ASCII-8BIT"
|
7
|
+
raise "MaskedString only operates on BINARY strings"
|
8
|
+
end
|
9
|
+
raise "Too short" if bytesize < 4 # TODO - change
|
10
|
+
@masking_key = String.new(self[0..3])
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create_mask
|
14
|
+
MaskedString.new "rAnD" #TODO make random 4 character string
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create_masked_string(original)
|
18
|
+
masked_string = MaskedString.new
|
19
|
+
masking_key = self.create_mask
|
20
|
+
masked_string << masking_key
|
21
|
+
original.size.times do |i|
|
22
|
+
char = original.getbyte(i)
|
23
|
+
masked_string << (char ^ masking_key.getbyte(i%4))
|
24
|
+
end
|
25
|
+
if masked_string.respond_to?(:force_encoding)
|
26
|
+
masked_string.force_encoding("ASCII-8BIT")
|
27
|
+
end
|
28
|
+
masked_string.read_mask # get input string
|
29
|
+
return masked_string
|
30
|
+
end
|
31
|
+
|
32
|
+
# Removes the mask, behaves like a normal string again
|
33
|
+
def unset_mask
|
34
|
+
@masking_key = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def slice_mask
|
38
|
+
slice!(0, 4)
|
39
|
+
end
|
40
|
+
|
41
|
+
def getbyte(index)
|
42
|
+
if @masking_key
|
43
|
+
masked_char = super
|
44
|
+
masked_char ? masked_char ^ @masking_key.getbyte(index % 4) : nil
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def getbytes(start_index, count)
|
51
|
+
data = ''
|
52
|
+
if @masking_key
|
53
|
+
count.times do |i|
|
54
|
+
data << getbyte(start_index + i)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
data = String.new(self[start_index..start_index+count])
|
58
|
+
end
|
59
|
+
data
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module MessageProcessor03
|
6
|
+
def message(message_type, extension_data, application_data)
|
7
|
+
case message_type
|
8
|
+
when :close
|
9
|
+
if @state == :closing
|
10
|
+
# TODO: Check that message body matches sent data
|
11
|
+
# We can close connection immediately since there is no more data
|
12
|
+
# is allowed to be sent or received on this connection
|
13
|
+
@connection.close_connection
|
14
|
+
@state = :closed
|
15
|
+
else
|
16
|
+
# Acknowlege close
|
17
|
+
# The connection is considered closed
|
18
|
+
send_frame(:close, application_data)
|
19
|
+
@state = :closed
|
20
|
+
@connection.close_connection_after_writing
|
21
|
+
end
|
22
|
+
when :ping
|
23
|
+
# Pong back the same data
|
24
|
+
send_frame(:pong, application_data)
|
25
|
+
when :pong
|
26
|
+
# TODO: Do something. Complete a deferrable established by a ping?
|
27
|
+
when :text
|
28
|
+
if application_data.respond_to?(:force_encoding)
|
29
|
+
application_data.force_encoding("UTF-8")
|
30
|
+
end
|
31
|
+
@connection.trigger_on_message(application_data)
|
32
|
+
when :binary
|
33
|
+
@connection.trigger_on_message(application_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
module MessageProcessor06
|
4
|
+
def message(message_type, extension_data, application_data)
|
5
|
+
debug [:message_received, message_type, application_data]
|
6
|
+
|
7
|
+
case message_type
|
8
|
+
when :close
|
9
|
+
status_code = case application_data.length
|
10
|
+
when 0
|
11
|
+
# close messages MAY contain a body
|
12
|
+
nil
|
13
|
+
when 1
|
14
|
+
# Illegal close frame
|
15
|
+
raise DataError, "Close frames with a body must contain a 2 byte status code"
|
16
|
+
else
|
17
|
+
application_data.slice!(0, 2).unpack('n').first
|
18
|
+
end
|
19
|
+
|
20
|
+
debug [:close_frame_received, status_code, application_data]
|
21
|
+
|
22
|
+
if @state == :closing
|
23
|
+
# We can close connection immediately since there is no more data
|
24
|
+
# is allowed to be sent or received on this connection
|
25
|
+
@connection.close_connection
|
26
|
+
@state = :closed
|
27
|
+
else
|
28
|
+
# Acknowlege close
|
29
|
+
# The connection is considered closed
|
30
|
+
send_frame(:close, '')
|
31
|
+
@state = :closed
|
32
|
+
@connection.close_connection_after_writing
|
33
|
+
# TODO: Send close status code and body to app code
|
34
|
+
end
|
35
|
+
when :ping
|
36
|
+
# Pong back the same data
|
37
|
+
send_frame(:pong, application_data)
|
38
|
+
when :pong
|
39
|
+
# TODO: Do something. Complete a deferrable established by a ping?
|
40
|
+
@connection.trigger_on_message(application_data, :pong)
|
41
|
+
when :text
|
42
|
+
if application_data.respond_to?(:force_encoding)
|
43
|
+
application_data.force_encoding("UTF-8")
|
44
|
+
end
|
45
|
+
@connection.trigger_on_message(application_data, :text)
|
46
|
+
when :binary
|
47
|
+
@connection.trigger_on_message(application_data, :binary)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
class WebSocketError < RuntimeError; end
|
4
|
+
class HandshakeError < WebSocketError; end
|
5
|
+
class DataError < WebSocketError; end
|
6
|
+
|
7
|
+
#backwards compatibility
|
8
|
+
def self.start(options, &blk)
|
9
|
+
self.start_ws_server(options, &blk)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.start_ws_server(options, &blk)
|
13
|
+
EM.epoll
|
14
|
+
EM.run do
|
15
|
+
|
16
|
+
#trap("TERM") { stop; }
|
17
|
+
#trap("INT") { stop; }
|
18
|
+
|
19
|
+
EventMachine::start_server(options[:host], options[:port],
|
20
|
+
EventMachine::WebSocket::Connection, options) do |c|
|
21
|
+
blk.call(c)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.start_ws_client(options, &blk)
|
27
|
+
EM.epoll
|
28
|
+
EM.run do
|
29
|
+
|
30
|
+
#trap("TERM") { stop; raise "TERM" }
|
31
|
+
#trap("INT") { stop; raise "INT" }
|
32
|
+
|
33
|
+
EM.connect(options[:host], options[:port],
|
34
|
+
EventMachine::WebSocket::ClientConnection, options) do |c|
|
35
|
+
blk.call(c)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.stop
|
41
|
+
puts "Terminating WebSocket Server"
|
42
|
+
EventMachine.stop
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/em-websocket.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require "eventmachine"
|
4
|
+
|
5
|
+
%w[
|
6
|
+
debugger websocket connection client_connection
|
7
|
+
handshake75 handshake76 handshake04
|
8
|
+
framing76 framing03 framing04 framing05 framing07
|
9
|
+
close75 close03 close05 close06
|
10
|
+
masking04
|
11
|
+
message_processor_03 message_processor_06
|
12
|
+
handler_factory handler handler75 handler76 handler03 handler05 handler06 handler07 handler08 handler13
|
13
|
+
].each do |file|
|
14
|
+
require "em-websocket/#{file}"
|
15
|
+
end
|
16
|
+
|
17
|
+
unless ''.respond_to?(:getbyte)
|
18
|
+
class String
|
19
|
+
def getbyte(i)
|
20
|
+
self[i]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "em-websocket"
|