_bushido-faye-websocket 0.4.4
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/CHANGELOG.txt +56 -0
- data/README.rdoc +366 -0
- data/examples/app.rb +50 -0
- data/examples/autobahn_client.rb +44 -0
- data/examples/client.rb +30 -0
- data/examples/config.ru +17 -0
- data/examples/haproxy.conf +21 -0
- data/examples/server.rb +44 -0
- data/examples/sse.html +39 -0
- data/examples/ws.html +44 -0
- data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +61 -0
- data/ext/faye_websocket_mask/extconf.rb +5 -0
- data/ext/faye_websocket_mask/faye_websocket_mask.c +33 -0
- data/lib/faye/adapters/goliath.rb +47 -0
- data/lib/faye/adapters/rainbows.rb +32 -0
- data/lib/faye/adapters/rainbows_client.rb +70 -0
- data/lib/faye/adapters/thin.rb +62 -0
- data/lib/faye/eventsource.rb +124 -0
- data/lib/faye/websocket.rb +216 -0
- data/lib/faye/websocket/adapter.rb +21 -0
- data/lib/faye/websocket/api.rb +96 -0
- data/lib/faye/websocket/api/event.rb +33 -0
- data/lib/faye/websocket/api/event_target.rb +34 -0
- data/lib/faye/websocket/client.rb +84 -0
- data/lib/faye/websocket/draft75_parser.rb +87 -0
- data/lib/faye/websocket/draft76_parser.rb +84 -0
- data/lib/faye/websocket/hybi_parser.rb +320 -0
- data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
- data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
- data/lib/faye/websocket/utf8_match.rb +8 -0
- data/spec/faye/websocket/client_spec.rb +179 -0
- data/spec/faye/websocket/draft75_parser_examples.rb +48 -0
- data/spec/faye/websocket/draft75_parser_spec.rb +27 -0
- data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
- data/spec/faye/websocket/hybi_parser_spec.rb +156 -0
- data/spec/rainbows.conf +3 -0
- data/spec/server.crt +15 -0
- data/spec/server.key +15 -0
- data/spec/spec_helper.rb +68 -0
- metadata +158 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Faye::WebSocket::API
|
2
|
+
class Event
|
3
|
+
|
4
|
+
attr_reader :type, :bubbles, :cancelable
|
5
|
+
attr_accessor :target, :current_target, :event_phase, :data
|
6
|
+
|
7
|
+
CAPTURING_PHASE = 1
|
8
|
+
AT_TARGET = 2
|
9
|
+
BUBBLING_PHASE = 3
|
10
|
+
|
11
|
+
def initialize(event_type, options = {})
|
12
|
+
@type = event_type
|
13
|
+
metaclass = (class << self ; self ; end)
|
14
|
+
options.each do |key, value|
|
15
|
+
metaclass.__send__(:define_method, key) { value }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def init_event(event_type, can_bubble, cancelable)
|
20
|
+
@type = event_type
|
21
|
+
@bubbles = can_bubble
|
22
|
+
@cancelable = cancelable
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_propagation
|
26
|
+
end
|
27
|
+
|
28
|
+
def prevent_default
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Faye::WebSocket::API
|
2
|
+
module EventTarget
|
3
|
+
|
4
|
+
attr_accessor :onopen, :onmessage, :onerror, :onclose
|
5
|
+
|
6
|
+
def add_event_listener(event_type, listener, use_capture = false)
|
7
|
+
@listeners ||= {}
|
8
|
+
list = @listeners[event_type] ||= []
|
9
|
+
list << listener
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_event_listener(event_type, listener, use_capture = false)
|
13
|
+
return unless @listeners and @listeners[event_type]
|
14
|
+
return @listeners.delete(event_type) unless listener
|
15
|
+
|
16
|
+
@listeners[event_type].delete_if(&listener.method(:==))
|
17
|
+
end
|
18
|
+
|
19
|
+
def dispatch_event(event)
|
20
|
+
event.target = event.current_target = self
|
21
|
+
event.event_phase = Event::AT_TARGET
|
22
|
+
|
23
|
+
callback = __send__("on#{ event.type }")
|
24
|
+
callback.call(event) if callback
|
25
|
+
|
26
|
+
return unless @listeners and @listeners[event.type]
|
27
|
+
@listeners[event.type].each do |listener|
|
28
|
+
listener.call(event)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
|
4
|
+
class Client
|
5
|
+
include API
|
6
|
+
attr_reader :protocol, :uri
|
7
|
+
|
8
|
+
def initialize(url, protocols = nil)
|
9
|
+
@parser = HybiParser.new(self, :masking => true, :protocols => protocols)
|
10
|
+
@url = url
|
11
|
+
@uri = URI.parse(url)
|
12
|
+
|
13
|
+
@protocol = ''
|
14
|
+
@ready_state = CONNECTING
|
15
|
+
@buffered_amount = 0
|
16
|
+
|
17
|
+
port = @uri.port || (@uri.scheme == 'wss' ? 443 : 80)
|
18
|
+
|
19
|
+
EventMachine.connect(@uri.host, port, Connection) do |conn|
|
20
|
+
@stream = conn
|
21
|
+
conn.parent = self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def on_connect
|
28
|
+
@stream.start_tls if @uri.scheme == 'wss'
|
29
|
+
@handshake = @parser.create_handshake
|
30
|
+
@message = []
|
31
|
+
@stream.write(@handshake.request_data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive_data(data)
|
35
|
+
data = WebSocket.encode(data)
|
36
|
+
|
37
|
+
case @ready_state
|
38
|
+
when CONNECTING then
|
39
|
+
@message += @handshake.parse(data)
|
40
|
+
return unless @handshake.complete?
|
41
|
+
|
42
|
+
if @handshake.valid?
|
43
|
+
@protocol = @handshake.protocol || ''
|
44
|
+
@ready_state = OPEN
|
45
|
+
event = Event.new('open')
|
46
|
+
event.init_event('open', false, false)
|
47
|
+
dispatch_event(event)
|
48
|
+
|
49
|
+
receive_data(@message)
|
50
|
+
else
|
51
|
+
@ready_state = CLOSED
|
52
|
+
event = Event.new('close', :code => 1006, :reason => '')
|
53
|
+
event.init_event('close', false, false)
|
54
|
+
dispatch_event(event)
|
55
|
+
end
|
56
|
+
|
57
|
+
when OPEN, CLOSING then
|
58
|
+
@parser.parse(data)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Connection
|
63
|
+
attr_accessor :parent
|
64
|
+
|
65
|
+
def connection_completed
|
66
|
+
parent.__send__(:on_connect)
|
67
|
+
end
|
68
|
+
|
69
|
+
def receive_data(data)
|
70
|
+
parent.__send__(:receive_data, data)
|
71
|
+
end
|
72
|
+
|
73
|
+
def unbind
|
74
|
+
parent.close(1006, '', false)
|
75
|
+
end
|
76
|
+
|
77
|
+
def write(data)
|
78
|
+
send_data(data) rescue nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
|
4
|
+
class Draft75Parser
|
5
|
+
attr_reader :protocol
|
6
|
+
|
7
|
+
def initialize(web_socket, options = {})
|
8
|
+
@socket = web_socket
|
9
|
+
@stage = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def version
|
13
|
+
'hixie-75'
|
14
|
+
end
|
15
|
+
|
16
|
+
def handshake_response
|
17
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
18
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
19
|
+
upgrade << "Connection: Upgrade\r\n"
|
20
|
+
upgrade << "WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
|
21
|
+
upgrade << "WebSocket-Location: #{@socket.url}\r\n"
|
22
|
+
upgrade << "\r\n"
|
23
|
+
upgrade
|
24
|
+
end
|
25
|
+
|
26
|
+
def open?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse(buffer)
|
31
|
+
buffer.each_byte do |data|
|
32
|
+
case @stage
|
33
|
+
when 0 then
|
34
|
+
parse_leading_byte(data)
|
35
|
+
|
36
|
+
when 1 then
|
37
|
+
value = (data & 0x7F)
|
38
|
+
@length = value + 128 * @length
|
39
|
+
|
40
|
+
if @closing and @length.zero?
|
41
|
+
@socket.close(nil, nil, false)
|
42
|
+
elsif (0x80 & data) != 0x80
|
43
|
+
if @length.zero?
|
44
|
+
@socket.receive('')
|
45
|
+
@stage = 0
|
46
|
+
else
|
47
|
+
@buffer = []
|
48
|
+
@stage = 2
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
when 2 then
|
53
|
+
if data == 0xFF
|
54
|
+
@socket.receive(WebSocket.encode(@buffer))
|
55
|
+
@stage = 0
|
56
|
+
else
|
57
|
+
@buffer << data
|
58
|
+
if @length and @buffer.size == @length
|
59
|
+
@stage = 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_leading_byte(data)
|
69
|
+
if (0x80 & data) == 0x80
|
70
|
+
@length = 0
|
71
|
+
@stage = 1
|
72
|
+
else
|
73
|
+
@length = nil
|
74
|
+
@buffer = []
|
75
|
+
@stage = 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def frame(data, type = nil, error_type = nil)
|
80
|
+
return WebSocket.encode(data) if Array === data
|
81
|
+
["\x00", data, "\xFF"].map(&WebSocket.method(:encode)) * ''
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
|
4
|
+
class Draft76Parser < Draft75Parser
|
5
|
+
def version
|
6
|
+
'hixie-76'
|
7
|
+
end
|
8
|
+
|
9
|
+
def handshake_response
|
10
|
+
env = @socket.env
|
11
|
+
signature = handshake_signature(env['rack.input'].read)
|
12
|
+
|
13
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
14
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
15
|
+
upgrade << "Connection: Upgrade\r\n"
|
16
|
+
upgrade << "Sec-WebSocket-Origin: #{env['HTTP_ORIGIN']}\r\n"
|
17
|
+
upgrade << "Sec-WebSocket-Location: #{@socket.url}\r\n"
|
18
|
+
upgrade << "\r\n"
|
19
|
+
upgrade << signature if signature
|
20
|
+
upgrade
|
21
|
+
end
|
22
|
+
|
23
|
+
def handshake_signature(head)
|
24
|
+
return nil if head.empty?
|
25
|
+
env = @socket.env
|
26
|
+
|
27
|
+
key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
|
28
|
+
value1 = number_from_key(key1) / spaces_in_key(key1)
|
29
|
+
|
30
|
+
key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
|
31
|
+
value2 = number_from_key(key2) / spaces_in_key(key2)
|
32
|
+
|
33
|
+
@handshake_complete = true
|
34
|
+
|
35
|
+
Digest::MD5.digest(big_endian(value1) +
|
36
|
+
big_endian(value2) +
|
37
|
+
head)
|
38
|
+
end
|
39
|
+
|
40
|
+
def open?
|
41
|
+
!!@handshake_complete
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse(data)
|
45
|
+
return super if @handshake_complete
|
46
|
+
handshake_signature(data)
|
47
|
+
end
|
48
|
+
|
49
|
+
def close(code = nil, reason = nil, &callback)
|
50
|
+
return if @closed
|
51
|
+
@socket.send([0xFF, 0x00]) if @closing
|
52
|
+
@closed = true
|
53
|
+
callback.call if callback
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_leading_byte(data)
|
59
|
+
return super unless data == 0xFF
|
60
|
+
@closing = true
|
61
|
+
@length = 0
|
62
|
+
@stage = 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def number_from_key(key)
|
66
|
+
key.scan(/[0-9]/).join('').to_i(10)
|
67
|
+
end
|
68
|
+
|
69
|
+
def spaces_in_key(key)
|
70
|
+
key.scan(/ /).size
|
71
|
+
end
|
72
|
+
|
73
|
+
def big_endian(number)
|
74
|
+
string = ''
|
75
|
+
[24,16,8,0].each do |offset|
|
76
|
+
string << (number >> offset & 0xFF).chr
|
77
|
+
end
|
78
|
+
string
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,320 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
|
4
|
+
class HybiParser
|
5
|
+
root = File.expand_path('../hybi_parser', __FILE__)
|
6
|
+
autoload :Handshake, root + '/handshake'
|
7
|
+
autoload :StreamReader, root + '/stream_reader'
|
8
|
+
|
9
|
+
BYTE = 0b11111111
|
10
|
+
FIN = MASK = 0b10000000
|
11
|
+
RSV1 = 0b01000000
|
12
|
+
RSV2 = 0b00100000
|
13
|
+
RSV3 = 0b00010000
|
14
|
+
OPCODE = 0b00001111
|
15
|
+
LENGTH = 0b01111111
|
16
|
+
|
17
|
+
OPCODES = {
|
18
|
+
:continuation => 0,
|
19
|
+
:text => 1,
|
20
|
+
:binary => 2,
|
21
|
+
:close => 8,
|
22
|
+
:ping => 9,
|
23
|
+
:pong => 10
|
24
|
+
}
|
25
|
+
|
26
|
+
FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
|
27
|
+
OPENING_OPCODES = OPCODES.values_at(:text, :binary)
|
28
|
+
|
29
|
+
ERRORS = {
|
30
|
+
:normal_closure => 1000,
|
31
|
+
:going_away => 1001,
|
32
|
+
:protocol_error => 1002,
|
33
|
+
:unacceptable => 1003,
|
34
|
+
:encoding_error => 1007,
|
35
|
+
:policy_violation => 1008,
|
36
|
+
:too_large => 1009,
|
37
|
+
:extension_error => 1010
|
38
|
+
}
|
39
|
+
|
40
|
+
ERROR_CODES = ERRORS.values
|
41
|
+
|
42
|
+
attr_reader :protocol
|
43
|
+
|
44
|
+
def initialize(web_socket, options = {})
|
45
|
+
reset
|
46
|
+
@socket = web_socket
|
47
|
+
@reader = StreamReader.new
|
48
|
+
@stage = 0
|
49
|
+
@masking = options[:masking]
|
50
|
+
@protocols = options[:protocols]
|
51
|
+
@protocols = @protocols.split(/\s*,\s*/) if String === @protocols
|
52
|
+
|
53
|
+
@ping_callbacks = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def version
|
57
|
+
"hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def handshake_response
|
61
|
+
sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
|
62
|
+
return '' unless String === sec_key
|
63
|
+
|
64
|
+
accept = Base64.encode64(Digest::SHA1.digest(sec_key + Handshake::GUID)).strip
|
65
|
+
protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
66
|
+
supported = @protocols
|
67
|
+
proto = nil
|
68
|
+
|
69
|
+
headers = [
|
70
|
+
"HTTP/1.1 101 Switching Protocols",
|
71
|
+
"Upgrade: websocket",
|
72
|
+
"Connection: Upgrade",
|
73
|
+
"Sec-WebSocket-Accept: #{accept}"
|
74
|
+
]
|
75
|
+
|
76
|
+
if protos and supported
|
77
|
+
protos = protos.split(/\s*,\s*/) if String === protos
|
78
|
+
proto = protos.find { |p| supported.include?(p) }
|
79
|
+
if proto
|
80
|
+
@protocol = proto
|
81
|
+
headers << "Sec-WebSocket-Protocol: #{proto}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
(headers + ['','']).join("\r\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_handshake
|
89
|
+
Handshake.new(@socket.uri, @protocols)
|
90
|
+
end
|
91
|
+
|
92
|
+
def open?
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse(data)
|
97
|
+
@reader.put(data.bytes.to_a)
|
98
|
+
buffer = true
|
99
|
+
while buffer
|
100
|
+
case @stage
|
101
|
+
when 0 then
|
102
|
+
buffer = @reader.read(1)
|
103
|
+
parse_opcode(buffer[0]) if buffer
|
104
|
+
|
105
|
+
when 1 then
|
106
|
+
buffer = @reader.read(1)
|
107
|
+
parse_length(buffer[0]) if buffer
|
108
|
+
|
109
|
+
when 2 then
|
110
|
+
buffer = @reader.read(@length_size)
|
111
|
+
parse_extended_length(buffer) if buffer
|
112
|
+
|
113
|
+
when 3 then
|
114
|
+
buffer = @reader.read(4)
|
115
|
+
if buffer
|
116
|
+
@mask = buffer
|
117
|
+
@stage = 4
|
118
|
+
end
|
119
|
+
|
120
|
+
when 4 then
|
121
|
+
buffer = @reader.read(@length)
|
122
|
+
if buffer
|
123
|
+
@payload = buffer
|
124
|
+
emit_frame
|
125
|
+
@stage = 0
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def frame(data, type = nil, code = nil)
|
134
|
+
return nil if @closed
|
135
|
+
|
136
|
+
is_text = (String === data)
|
137
|
+
opcode = OPCODES[type || (is_text ? :text : :binary)]
|
138
|
+
buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
|
139
|
+
insert = code ? 2 : 0
|
140
|
+
length = buffer.size + insert
|
141
|
+
header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
|
142
|
+
offset = header + (@masking ? 4 : 0)
|
143
|
+
masked = @masking ? MASK : 0
|
144
|
+
frame = Array.new(offset)
|
145
|
+
|
146
|
+
frame[0] = FIN | opcode
|
147
|
+
|
148
|
+
if length <= 125
|
149
|
+
frame[1] = masked | length
|
150
|
+
elsif length <= 65535
|
151
|
+
frame[1] = masked | 126
|
152
|
+
frame[2] = (length >> 8) & BYTE
|
153
|
+
frame[3] = length & BYTE
|
154
|
+
else
|
155
|
+
frame[1] = masked | 127
|
156
|
+
frame[2] = (length >> 56) & BYTE
|
157
|
+
frame[3] = (length >> 48) & BYTE
|
158
|
+
frame[4] = (length >> 40) & BYTE
|
159
|
+
frame[5] = (length >> 32) & BYTE
|
160
|
+
frame[6] = (length >> 24) & BYTE
|
161
|
+
frame[7] = (length >> 16) & BYTE
|
162
|
+
frame[8] = (length >> 8) & BYTE
|
163
|
+
frame[9] = length & BYTE
|
164
|
+
end
|
165
|
+
|
166
|
+
if code
|
167
|
+
buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
|
168
|
+
end
|
169
|
+
|
170
|
+
if @masking
|
171
|
+
mask = [rand(256), rand(256), rand(256), rand(256)]
|
172
|
+
frame[header...offset] = mask
|
173
|
+
buffer = WebSocketMask.mask(buffer, mask)
|
174
|
+
end
|
175
|
+
|
176
|
+
frame.concat(buffer)
|
177
|
+
|
178
|
+
WebSocket.encode(frame)
|
179
|
+
end
|
180
|
+
|
181
|
+
def ping(message = '', &callback)
|
182
|
+
@ping_callbacks[message] = callback if callback
|
183
|
+
@socket.send(message, :ping)
|
184
|
+
end
|
185
|
+
|
186
|
+
def close(code = nil, reason = nil, &callback)
|
187
|
+
return if @closed
|
188
|
+
@closing_callback ||= callback
|
189
|
+
@socket.send(reason || '', :close, code || ERRORS[:normal_closure])
|
190
|
+
@closed = true
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def parse_opcode(data)
|
196
|
+
if [RSV1, RSV2, RSV3].any? { |rsv| (data & rsv) == rsv }
|
197
|
+
return @socket.close(ERRORS[:protocol_error], nil, false)
|
198
|
+
end
|
199
|
+
|
200
|
+
@final = (data & FIN) == FIN
|
201
|
+
@opcode = (data & OPCODE)
|
202
|
+
@mask = []
|
203
|
+
@payload = []
|
204
|
+
|
205
|
+
unless OPCODES.values.include?(@opcode)
|
206
|
+
return @socket.close(ERRORS[:protocol_error], nil, false)
|
207
|
+
end
|
208
|
+
|
209
|
+
unless FRAGMENTED_OPCODES.include?(@opcode) or @final
|
210
|
+
return @socket.close(ERRORS[:protocol_error], nil, false)
|
211
|
+
end
|
212
|
+
|
213
|
+
if @mode and OPENING_OPCODES.include?(@opcode)
|
214
|
+
return @socket.close(ERRORS[:protocol_error], nil, false)
|
215
|
+
end
|
216
|
+
|
217
|
+
@stage = 1
|
218
|
+
end
|
219
|
+
|
220
|
+
def parse_length(data)
|
221
|
+
@masked = (data & MASK) == MASK
|
222
|
+
@length = (data & LENGTH)
|
223
|
+
|
224
|
+
if @length <= 125
|
225
|
+
@stage = @masked ? 3 : 4
|
226
|
+
else
|
227
|
+
@length_size = (@length == 126) ? 2 : 8
|
228
|
+
@stage = 2
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def parse_extended_length(buffer)
|
233
|
+
@length = integer(buffer)
|
234
|
+
@stage = @masked ? 3 : 4
|
235
|
+
end
|
236
|
+
|
237
|
+
def emit_frame
|
238
|
+
payload = @masked ? WebSocketMask.mask(@payload, @mask) : @payload
|
239
|
+
|
240
|
+
case @opcode
|
241
|
+
when OPCODES[:continuation] then
|
242
|
+
return @socket.close(ERRORS[:protocol_error], nil, false) unless @mode
|
243
|
+
@buffer.concat(payload)
|
244
|
+
if @final
|
245
|
+
message = @buffer
|
246
|
+
message = WebSocket.encode(message, true) if @mode == :text
|
247
|
+
reset
|
248
|
+
if message
|
249
|
+
@socket.receive(message)
|
250
|
+
else
|
251
|
+
@socket.close(ERRORS[:encoding_error], nil, false)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
when OPCODES[:text] then
|
256
|
+
if @final
|
257
|
+
message = WebSocket.encode(payload, true)
|
258
|
+
if message
|
259
|
+
@socket.receive(message)
|
260
|
+
else
|
261
|
+
@socket.close(ERRORS[:encoding_error], nil, false)
|
262
|
+
end
|
263
|
+
else
|
264
|
+
@mode = :text
|
265
|
+
@buffer.concat(payload)
|
266
|
+
end
|
267
|
+
|
268
|
+
when OPCODES[:binary] then
|
269
|
+
if @final
|
270
|
+
@socket.receive(payload)
|
271
|
+
else
|
272
|
+
@mode = :binary
|
273
|
+
@buffer.concat(payload)
|
274
|
+
end
|
275
|
+
|
276
|
+
when OPCODES[:close] then
|
277
|
+
code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
|
278
|
+
|
279
|
+
unless (payload.size == 0) or
|
280
|
+
(code && code >= 3000 && code < 5000) or
|
281
|
+
ERROR_CODES.include?(code)
|
282
|
+
code = ERRORS[:protocol_error]
|
283
|
+
end
|
284
|
+
|
285
|
+
if payload.size > 125 or not WebSocket.valid_utf8?(payload[2..-1] || [])
|
286
|
+
code = ERRORS[:protocol_error]
|
287
|
+
end
|
288
|
+
|
289
|
+
reason = (payload.size > 2) ? WebSocket.encode(payload[2..-1], true) : nil
|
290
|
+
@socket.close(code, reason, false)
|
291
|
+
@closing_callback.call if @closing_callback
|
292
|
+
|
293
|
+
when OPCODES[:ping] then
|
294
|
+
return @socket.close(ERRORS[:protocol_error], nil, false) if payload.size > 125
|
295
|
+
@socket.send(payload, :pong)
|
296
|
+
|
297
|
+
when OPCODES[:pong] then
|
298
|
+
message = WebSocket.encode(payload, true)
|
299
|
+
callback = @ping_callbacks[message]
|
300
|
+
@ping_callbacks.delete(message)
|
301
|
+
callback.call if callback
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def reset
|
306
|
+
@buffer = []
|
307
|
+
@mode = nil
|
308
|
+
end
|
309
|
+
|
310
|
+
def integer(bytes)
|
311
|
+
number = 0
|
312
|
+
bytes.each_with_index do |data, i|
|
313
|
+
number += data << (8 * (bytes.size - 1 - i))
|
314
|
+
end
|
315
|
+
number
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
end
|