_bushido-faye-websocket 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- 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,78 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
class HybiParser
|
4
|
+
|
5
|
+
class Handshake
|
6
|
+
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
7
|
+
|
8
|
+
attr_reader :protocol
|
9
|
+
|
10
|
+
def initialize(uri, protocols)
|
11
|
+
@uri = uri
|
12
|
+
@protocols = protocols
|
13
|
+
@key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
|
14
|
+
@accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
|
15
|
+
@buffer = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def request_data
|
19
|
+
hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
|
20
|
+
|
21
|
+
headers = [
|
22
|
+
"GET #{@uri.path}#{@uri.query ? '?' : ''}#{@uri.query} HTTP/1.1",
|
23
|
+
"Host: #{hostname}",
|
24
|
+
"Upgrade: websocket",
|
25
|
+
"Connection: Upgrade",
|
26
|
+
"Sec-WebSocket-Key: #{@key}",
|
27
|
+
"Sec-WebSocket-Version: 13"
|
28
|
+
]
|
29
|
+
|
30
|
+
if @protocols
|
31
|
+
headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
|
32
|
+
end
|
33
|
+
|
34
|
+
(headers + ['','']).join("\r\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse(data)
|
38
|
+
message = []
|
39
|
+
complete = false
|
40
|
+
data.each_byte do |byte|
|
41
|
+
if complete
|
42
|
+
message << byte
|
43
|
+
else
|
44
|
+
@buffer << byte
|
45
|
+
complete ||= complete?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
message
|
49
|
+
end
|
50
|
+
|
51
|
+
def complete?
|
52
|
+
@buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid?
|
56
|
+
data = WebSocket.encode(@buffer)
|
57
|
+
|
58
|
+
response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
|
59
|
+
return false unless response.code.to_i == 101
|
60
|
+
|
61
|
+
upgrade = response['Upgrade']
|
62
|
+
connection = response['Connection']
|
63
|
+
protocol = response['Sec-WebSocket-Protocol']
|
64
|
+
|
65
|
+
@protocol = @protocols && @protocols.include?(protocol) ?
|
66
|
+
protocol :
|
67
|
+
nil
|
68
|
+
|
69
|
+
upgrade and upgrade =~ /^websocket$/i and
|
70
|
+
connection and connection.split(/\s*,\s*/).include?('Upgrade') and
|
71
|
+
((!@protocols and !protocol) or @protocol) and
|
72
|
+
response['Sec-WebSocket-Accept'] == @accept
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
class HybiParser
|
4
|
+
|
5
|
+
class StreamReader
|
6
|
+
def initialize
|
7
|
+
@queue = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def read(length)
|
11
|
+
read_bytes(length)
|
12
|
+
end
|
13
|
+
|
14
|
+
def put(bytes)
|
15
|
+
return unless bytes and bytes.size > 0
|
16
|
+
@queue.concat(bytes)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def read_bytes(length)
|
22
|
+
return nil if length > @queue.size
|
23
|
+
@queue.shift(length)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module Faye
|
2
|
+
class WebSocket
|
3
|
+
|
4
|
+
# http://www.w3.org/International/questions/qa-forms-utf-8.en.php
|
5
|
+
UTF8_MATCH = /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/
|
6
|
+
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# encoding=utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
WebSocketSteps = EM::RSpec.async_steps do
|
6
|
+
def server(port, backend, secure, &callback)
|
7
|
+
@server = EchoServer.new
|
8
|
+
@server.listen(port, backend, secure)
|
9
|
+
@port = port
|
10
|
+
EM.add_timer(0.1, &callback)
|
11
|
+
end
|
12
|
+
|
13
|
+
def stop(&callback)
|
14
|
+
@server.stop
|
15
|
+
EM.next_tick(&callback)
|
16
|
+
end
|
17
|
+
|
18
|
+
def open_socket(url, protocols, &callback)
|
19
|
+
done = false
|
20
|
+
|
21
|
+
resume = lambda do |open|
|
22
|
+
unless done
|
23
|
+
done = true
|
24
|
+
@open = open
|
25
|
+
callback.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@ws = Faye::WebSocket::Client.new(url, protocols)
|
30
|
+
|
31
|
+
@ws.onopen = lambda { |e| resume.call(true) }
|
32
|
+
@ws.onclose = lambda { |e| resume.call(false) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def close_socket(&callback)
|
36
|
+
@ws.onclose = lambda do |e|
|
37
|
+
@open = false
|
38
|
+
callback.call
|
39
|
+
end
|
40
|
+
@ws.close
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_open(&callback)
|
44
|
+
@open.should == true
|
45
|
+
callback.call
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_closed(&callback)
|
49
|
+
@open.should == false
|
50
|
+
callback.call
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_protocol(protocol, &callback)
|
54
|
+
@ws.protocol.should == protocol
|
55
|
+
callback.call
|
56
|
+
end
|
57
|
+
|
58
|
+
def listen_for_message(&callback)
|
59
|
+
@ws.add_event_listener('message', lambda { |e| @message = e.data })
|
60
|
+
callback.call
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_message(message, &callback)
|
64
|
+
@ws.send(message)
|
65
|
+
EM.add_timer(0.1, &callback)
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_response(message, &callback)
|
69
|
+
@message.should == message
|
70
|
+
callback.call
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_no_response(&callback)
|
74
|
+
@message.should == nil
|
75
|
+
callback.call
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe Faye::WebSocket::Client do
|
80
|
+
next if Faye::WebSocket.jruby?
|
81
|
+
include WebSocketSteps
|
82
|
+
|
83
|
+
let(:protocols) { ["foo", "echo"] }
|
84
|
+
let(:plain_text_url) { "ws://0.0.0.0:8000/" }
|
85
|
+
let(:secure_url) { "wss://0.0.0.0:8000/" }
|
86
|
+
|
87
|
+
before do
|
88
|
+
Thread.new { EM.run }
|
89
|
+
sleep(0.1) until EM.reactor_running?
|
90
|
+
end
|
91
|
+
|
92
|
+
shared_examples_for "socket client" do
|
93
|
+
it "can open a connection" do
|
94
|
+
open_socket(socket_url, protocols)
|
95
|
+
check_open
|
96
|
+
check_protocol("echo")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "cannot open a connection to the wrong host" do
|
100
|
+
open_socket(blocked_url, protocols)
|
101
|
+
check_closed
|
102
|
+
end
|
103
|
+
|
104
|
+
it "cannot open a connection with unacceptable protocols" do
|
105
|
+
open_socket(socket_url, ["foo"])
|
106
|
+
check_closed
|
107
|
+
end
|
108
|
+
|
109
|
+
it "can close the connection" do
|
110
|
+
open_socket(socket_url, protocols)
|
111
|
+
close_socket
|
112
|
+
check_closed
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "in the OPEN state" do
|
116
|
+
before { open_socket(socket_url, protocols) }
|
117
|
+
|
118
|
+
it "can send and receive messages" do
|
119
|
+
listen_for_message
|
120
|
+
send_message "I expect this to be echoed"
|
121
|
+
check_response "I expect this to be echoed"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "sends numbers as strings" do
|
125
|
+
listen_for_message
|
126
|
+
send_message 13
|
127
|
+
check_response "13"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "in the CLOSED state" do
|
132
|
+
before do
|
133
|
+
open_socket(socket_url, protocols)
|
134
|
+
close_socket
|
135
|
+
end
|
136
|
+
|
137
|
+
it "cannot send and receive messages" do
|
138
|
+
listen_for_message
|
139
|
+
send_message "I expect this to be echoed"
|
140
|
+
check_no_response
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "with a plain-text Thin server" do
|
146
|
+
let(:socket_url) { plain_text_url }
|
147
|
+
let(:blocked_url) { secure_url }
|
148
|
+
|
149
|
+
before { server 8000, :thin, false }
|
150
|
+
after { sync ; stop }
|
151
|
+
|
152
|
+
it_should_behave_like "socket client"
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "with a plain-text Rainbows server" do
|
156
|
+
next if Faye::WebSocket.rbx?
|
157
|
+
|
158
|
+
let(:socket_url) { plain_text_url }
|
159
|
+
let(:blocked_url) { secure_url }
|
160
|
+
|
161
|
+
before { server 8000, :rainbows, false }
|
162
|
+
after { sync ; stop }
|
163
|
+
|
164
|
+
it_should_behave_like "socket client"
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "with a secure Thin server" do
|
168
|
+
next if Faye::WebSocket.rbx?
|
169
|
+
|
170
|
+
let(:socket_url) { secure_url }
|
171
|
+
let(:blocked_url) { plain_text_url }
|
172
|
+
|
173
|
+
before { server 8000, :thin, true }
|
174
|
+
after { sync ; stop }
|
175
|
+
|
176
|
+
it_should_behave_like "socket client"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding=utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
shared_examples_for "draft-75 parser" do
|
6
|
+
it "parses text frames" do
|
7
|
+
@web_socket.should_receive(:receive).with("Hello")
|
8
|
+
parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses multiple frames from the same packet" do
|
12
|
+
@web_socket.should_receive(:receive).with("Hello").exactly(2)
|
13
|
+
parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "parses text frames beginning 0x00-0x7F" do
|
17
|
+
@web_socket.should_receive(:receive).with("Hello")
|
18
|
+
parse [0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "ignores frames with a length header" do
|
22
|
+
@web_socket.should_not_receive(:receive)
|
23
|
+
parse [0x80, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "parses text following an ignored block" do
|
27
|
+
@web_socket.should_receive(:receive).with("Hello")
|
28
|
+
parse [0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "parses multibyte text frames" do
|
32
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
33
|
+
parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "parses frames received in several packets" do
|
37
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
38
|
+
parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]
|
39
|
+
parse [0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "parses fragmented frames" do
|
43
|
+
@web_socket.should_receive(:receive).with("Hello")
|
44
|
+
parse [0x00, 0x48, 0x65, 0x6c]
|
45
|
+
parse [0x6c, 0x6f, 0xff]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding=utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Faye::WebSocket::Draft75Parser do
|
6
|
+
include EncodingHelper
|
7
|
+
|
8
|
+
before do
|
9
|
+
@web_socket = mock Faye::WebSocket
|
10
|
+
@parser = Faye::WebSocket::Draft75Parser.new(@web_socket)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe :parse do
|
14
|
+
it_should_behave_like "draft-75 parser"
|
15
|
+
end
|
16
|
+
|
17
|
+
describe :frame do
|
18
|
+
it "returns the given string formatted as a WebSocket frame" do
|
19
|
+
bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "encodes multibyte characters correctly" do
|
23
|
+
message = encode "Apple = "
|
24
|
+
bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding=utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Faye::WebSocket::Draft76Parser do
|
6
|
+
include EncodingHelper
|
7
|
+
|
8
|
+
before do
|
9
|
+
@web_socket = mock Faye::WebSocket
|
10
|
+
@parser = Faye::WebSocket::Draft76Parser.new(@web_socket)
|
11
|
+
@parser.instance_eval { @handshake_complete = true }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe :parse do
|
15
|
+
it_should_behave_like "draft-75 parser"
|
16
|
+
|
17
|
+
it "closes the socket if a close frame is received" do
|
18
|
+
@web_socket.should_receive(:close)
|
19
|
+
parse [0xFF, 0x00]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe :frame do
|
24
|
+
it "returns the given string formatted as a WebSocket frame" do
|
25
|
+
bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "encodes multibyte characters correctly" do
|
29
|
+
message = encode "Apple = "
|
30
|
+
bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding=utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Faye::WebSocket::HybiParser do
|
6
|
+
include EncodingHelper
|
7
|
+
|
8
|
+
before do
|
9
|
+
@web_socket = mock Faye::WebSocket
|
10
|
+
@parser = Faye::WebSocket::HybiParser.new(@web_socket)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe :parse do
|
14
|
+
let(:mask) { (1..4).map { rand 255 } }
|
15
|
+
|
16
|
+
def mask_message(*bytes)
|
17
|
+
output = []
|
18
|
+
bytes.each_with_index do |byte, i|
|
19
|
+
output[i] = byte ^ mask[i % 4]
|
20
|
+
end
|
21
|
+
output
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses unmasked text frames" do
|
25
|
+
@web_socket.should_receive(:receive).with("Hello")
|
26
|
+
parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "parses multiple frames from the same packet" do
|
30
|
+
@web_socket.should_receive(:receive).with("Hello").exactly(2)
|
31
|
+
parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "parses empty text frames" do
|
35
|
+
@web_socket.should_receive(:receive).with("")
|
36
|
+
parse [0x81, 0x00]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "parses fragmented text frames" do
|
40
|
+
@web_socket.should_receive(:receive).with("Hello")
|
41
|
+
parse [0x01, 0x03, 0x48, 0x65, 0x6c]
|
42
|
+
parse [0x80, 0x02, 0x6c, 0x6f]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "parses masked text frames" do
|
46
|
+
@web_socket.should_receive(:receive).with("Hello")
|
47
|
+
parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "parses masked empty text frames" do
|
51
|
+
@web_socket.should_receive(:receive).with("")
|
52
|
+
parse [0x81, 0x80] + mask + mask_message()
|
53
|
+
end
|
54
|
+
|
55
|
+
it "parses masked fragmented text frames" do
|
56
|
+
@web_socket.should_receive(:receive).with("Hello")
|
57
|
+
parse [0x01, 0x81] + mask + mask_message(0x48)
|
58
|
+
parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "closes the socket if the frame has an unrecognized opcode" do
|
62
|
+
@web_socket.should_receive(:close).with(1002, nil, false)
|
63
|
+
parse [0x83, 0x00]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "closes the socket if a close frame is received" do
|
67
|
+
@web_socket.should_receive(:close).with(1000, "Hello", false)
|
68
|
+
parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "parses unmasked multibyte text frames" do
|
72
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
73
|
+
parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "parses frames received in several packets" do
|
77
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
78
|
+
parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c]
|
79
|
+
parse [0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "parses fragmented multibyte text frames" do
|
83
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
84
|
+
parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
|
85
|
+
parse [0x80, 0x01, 0xbf]
|
86
|
+
end
|
87
|
+
|
88
|
+
it "parses masked multibyte text frames" do
|
89
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
90
|
+
parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "parses masked fragmented multibyte text frames" do
|
94
|
+
@web_socket.should_receive(:receive).with(encode "Apple = ")
|
95
|
+
parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
|
96
|
+
parse [0x80, 0x81] + mask + mask_message(0xbf)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "parses unmasked medium-length text frames" do
|
100
|
+
@web_socket.should_receive(:receive).with("Hello" * 40)
|
101
|
+
parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
|
102
|
+
end
|
103
|
+
|
104
|
+
it "parses masked medium-length text frames" do
|
105
|
+
@web_socket.should_receive(:receive).with("Hello" * 40)
|
106
|
+
parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
|
107
|
+
end
|
108
|
+
|
109
|
+
it "replies to pings with a pong" do
|
110
|
+
@web_socket.should_receive(:send).with([0x4f, 0x48, 0x41, 0x49], :pong)
|
111
|
+
parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe :frame do
|
116
|
+
it "returns the given string formatted as a WebSocket frame" do
|
117
|
+
bytes(@parser.frame "Hello").should == [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
118
|
+
end
|
119
|
+
|
120
|
+
it "encodes multibyte characters correctly" do
|
121
|
+
message = encode "Apple = "
|
122
|
+
bytes(@parser.frame message).should == [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
123
|
+
end
|
124
|
+
|
125
|
+
it "encodes medium-length strings using extra length bytes" do
|
126
|
+
message = "Hello" * 40
|
127
|
+
bytes(@parser.frame message).should == [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
|
128
|
+
end
|
129
|
+
|
130
|
+
it "encodes long strings using extra length bytes" do
|
131
|
+
message = "Hello" * 13108
|
132
|
+
bytes(@parser.frame message).should == [0x81, 0x7f] +
|
133
|
+
[0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04] +
|
134
|
+
[0x48, 0x65, 0x6c, 0x6c, 0x6f] * 13108
|
135
|
+
end
|
136
|
+
|
137
|
+
it "encodes close frames with an error code" do
|
138
|
+
frame = @parser.frame "Hello", :close, 1002
|
139
|
+
bytes(frame).should == [0x88, 0x07, 0x03, 0xea, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
140
|
+
end
|
141
|
+
|
142
|
+
it "encodes pong frames" do
|
143
|
+
bytes(@parser.frame '', :pong).should == [0x8a, 0x00]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe :utf8 do
|
148
|
+
it "detects valid UTF-8" do
|
149
|
+
Faye::WebSocket.valid_utf8?( [72, 101, 108, 108, 111, 45, 194, 181, 64, 195, 159, 195, 182, 195, 164, 195, 188, 195, 160, 195, 161, 45, 85, 84, 70, 45, 56, 33, 33] ).should be_true
|
150
|
+
end
|
151
|
+
|
152
|
+
it "detects invalid UTF-8" do
|
153
|
+
Faye::WebSocket.valid_utf8?( [206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181, 237, 160, 128, 101, 100, 105, 116, 101, 100] ).should be_false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|