websocket-rack 0.1.0
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 +2 -0
- data/Gemfile +3 -0
- data/README.md +105 -0
- data/Rakefile +11 -0
- data/example/example.ru +31 -0
- data/example/html/FABridge.js +604 -0
- data/example/html/WebSocketMain.swf +0 -0
- data/example/html/index.html +76 -0
- data/example/html/swfobject.js +4 -0
- data/example/html/web_socket.js +388 -0
- data/lib/rack/websocket/application.rb +65 -0
- data/lib/rack/websocket/connection.rb +105 -0
- data/lib/rack/websocket/debugger.rb +17 -0
- data/lib/rack/websocket/extensions/thin/connection.rb +71 -0
- data/lib/rack/websocket/extensions/thin.rb +16 -0
- data/lib/rack/websocket/framing03.rb +178 -0
- data/lib/rack/websocket/framing76.rb +115 -0
- data/lib/rack/websocket/handler.rb +43 -0
- data/lib/rack/websocket/handler03.rb +14 -0
- data/lib/rack/websocket/handler75.rb +8 -0
- data/lib/rack/websocket/handler76.rb +11 -0
- data/lib/rack/websocket/handler_factory.rb +61 -0
- data/lib/rack/websocket/handshake75.rb +21 -0
- data/lib/rack/websocket/handshake76.rb +71 -0
- data/lib/rack/websocket.rb +40 -0
- data/spec/helper.rb +44 -0
- data/spec/integration/draft03_spec.rb +252 -0
- data/spec/integration/draft76_spec.rb +212 -0
- data/spec/unit/framing_spec.rb +108 -0
- data/spec/unit/handler_spec.rb +136 -0
- data/spec/websocket_spec.rb +210 -0
- data/websocket-rack.gemspec +23 -0
- metadata +147 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
# require 'helper'
|
2
|
+
#
|
3
|
+
# describe "WebSocket server draft76" do
|
4
|
+
# before :each do
|
5
|
+
# @request = {
|
6
|
+
# :port => 80,
|
7
|
+
# :method => "GET",
|
8
|
+
# :path => "/demo",
|
9
|
+
# :headers => {
|
10
|
+
# 'Host' => 'example.com',
|
11
|
+
# 'Connection' => 'Upgrade',
|
12
|
+
# 'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
13
|
+
# 'Sec-WebSocket-Protocol' => 'sample',
|
14
|
+
# 'Upgrade' => 'WebSocket',
|
15
|
+
# 'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
16
|
+
# 'Origin' => 'http://example.com'
|
17
|
+
# },
|
18
|
+
# :body => '^n:ds[4U'
|
19
|
+
# }
|
20
|
+
#
|
21
|
+
# @response = {
|
22
|
+
# :headers => {
|
23
|
+
# "Upgrade" => "WebSocket",
|
24
|
+
# "Connection" => "Upgrade",
|
25
|
+
# "Sec-WebSocket-Location" => "ws://example.com/demo",
|
26
|
+
# "Sec-WebSocket-Origin" => "http://example.com",
|
27
|
+
# "Sec-WebSocket-Protocol" => "sample"
|
28
|
+
# },
|
29
|
+
# :body => "8jKS\'y:G*Co,Wxa-"
|
30
|
+
# }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# it "should send back the correct handshake response" do
|
34
|
+
# EM.run do
|
35
|
+
# EM.add_timer(0.1) do
|
36
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
37
|
+
#
|
38
|
+
# # Create a fake client which sends draft 76 handshake
|
39
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
40
|
+
# connection.send_data(format_request(@request))
|
41
|
+
#
|
42
|
+
# connection.onopen = lambda {
|
43
|
+
# connection.handshake_response.lines.sort.
|
44
|
+
# should == format_response(@response).lines.sort
|
45
|
+
# EM.stop
|
46
|
+
# }
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# it "should send closing frame back and close the connection after recieving closing frame" do
|
52
|
+
# EM.run do
|
53
|
+
# EM.add_timer(0.1) do
|
54
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
55
|
+
#
|
56
|
+
# # Create a fake client which sends draft 76 handshake
|
57
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
58
|
+
# connection.send_data(format_request(@request))
|
59
|
+
#
|
60
|
+
# # Send closing frame after handshake complete
|
61
|
+
# connection.onopen = lambda {
|
62
|
+
# connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
63
|
+
# }
|
64
|
+
#
|
65
|
+
# # Check that this causes a termination string to be returned and the
|
66
|
+
# # connection close
|
67
|
+
# connection.onclose = lambda {
|
68
|
+
# connection.packets[0].should ==
|
69
|
+
# EM::WebSocket::Handler76::TERMINATE_STRING
|
70
|
+
# EM.stop
|
71
|
+
# }
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# it "should ignore any data received after the closing frame" do
|
77
|
+
# EM.run do
|
78
|
+
# EM.add_timer(0.1) do
|
79
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
80
|
+
# # Fail if foobar message is received
|
81
|
+
# ws.onmessage { |msg|
|
82
|
+
# failed
|
83
|
+
# }
|
84
|
+
# }
|
85
|
+
#
|
86
|
+
# # Create a fake client which sends draft 76 handshake
|
87
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
88
|
+
# connection.send_data(format_request(@request))
|
89
|
+
#
|
90
|
+
# # Send closing frame after handshake complete, followed by another msg
|
91
|
+
# connection.onopen = lambda {
|
92
|
+
# connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
93
|
+
# connection.send('foobar')
|
94
|
+
# }
|
95
|
+
#
|
96
|
+
# connection.onclose = lambda {
|
97
|
+
# EM.stop
|
98
|
+
# }
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# it "should accept null bytes within the frame after a line return" do
|
104
|
+
# EM.run do
|
105
|
+
# EM.add_timer(0.1) do
|
106
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
107
|
+
# ws.onmessage { |msg|
|
108
|
+
# msg.should == "\n\000"
|
109
|
+
# }
|
110
|
+
# }
|
111
|
+
#
|
112
|
+
# # Create a fake client which sends draft 76 handshake
|
113
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
114
|
+
# connection.send_data(format_request(@request))
|
115
|
+
#
|
116
|
+
# # Send closing frame after handshake complete
|
117
|
+
# connection.onopen = lambda {
|
118
|
+
# connection.send_data("\000\n\000\377")
|
119
|
+
# connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
120
|
+
# }
|
121
|
+
#
|
122
|
+
# connection.onclose = lambda {
|
123
|
+
# EM.stop
|
124
|
+
# }
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# it "should handle unreasonable frame lengths by calling onerror callback" do
|
130
|
+
# EM.run do
|
131
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
|
132
|
+
# server.onerror { |error|
|
133
|
+
# error.should be_an_instance_of EM::WebSocket::DataError
|
134
|
+
# error.message.should == "Frame length too long (1180591620717411303296 bytes)"
|
135
|
+
# EM.stop
|
136
|
+
# }
|
137
|
+
# }
|
138
|
+
#
|
139
|
+
# # Create a fake client which sends draft 76 handshake
|
140
|
+
# client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
141
|
+
# client.send_data(format_request(@request))
|
142
|
+
#
|
143
|
+
# # This particular frame indicates a message length of
|
144
|
+
# # 1180591620717411303296 bytes. Such a message would previously cause
|
145
|
+
# # a "bignum too big to convert into `long'" error.
|
146
|
+
# # However it is clearly unreasonable and should be rejected.
|
147
|
+
# client.onopen = lambda {
|
148
|
+
# client.send_data("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
|
149
|
+
# }
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# it "should handle impossible frames by calling onerror callback" do
|
154
|
+
# EM.run do
|
155
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
|
156
|
+
# server.onerror { |error|
|
157
|
+
# error.should be_an_instance_of EM::WebSocket::DataError
|
158
|
+
# error.message.should == "Invalid frame received"
|
159
|
+
# EM.stop
|
160
|
+
# }
|
161
|
+
# }
|
162
|
+
#
|
163
|
+
# # Create a fake client which sends draft 76 handshake
|
164
|
+
# client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
165
|
+
# client.send_data(format_request(@request))
|
166
|
+
#
|
167
|
+
# client.onopen = lambda {
|
168
|
+
# client.send_data("foobar") # Does not start with \x00 or \xff
|
169
|
+
# }
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# it "should handle invalid http requests by raising HandshakeError passed to onerror callback" do
|
174
|
+
# EM.run {
|
175
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
|
176
|
+
# server.onerror { |error|
|
177
|
+
# error.should be_an_instance_of EM::WebSocket::HandshakeError
|
178
|
+
# error.message.should == "Invalid HTTP header"
|
179
|
+
# EM.stop
|
180
|
+
# }
|
181
|
+
# }
|
182
|
+
#
|
183
|
+
# client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
184
|
+
# client.send_data("This is not a HTTP header\r\n\r\n")
|
185
|
+
# }
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# it "should handle handshake request split into two TCP packets" do
|
189
|
+
# EM.run do
|
190
|
+
# EM.add_timer(0.1) do
|
191
|
+
# EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
192
|
+
#
|
193
|
+
# # Create a fake client which sends draft 76 handshake
|
194
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
195
|
+
# data = format_request(@request)
|
196
|
+
# # Sends first half of the request
|
197
|
+
# connection.send_data(data[0...(data.length / 2)])
|
198
|
+
#
|
199
|
+
# connection.onopen = lambda {
|
200
|
+
# connection.handshake_response.lines.sort.
|
201
|
+
# should == format_response(@response).lines.sort
|
202
|
+
# EM.stop
|
203
|
+
# }
|
204
|
+
#
|
205
|
+
# EM.add_timer(0.1) do
|
206
|
+
# # Sends second half of the request
|
207
|
+
# connection.send_data(data[(data.length / 2)..-1])
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
# end
|
211
|
+
# end
|
212
|
+
# end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Rack::WebSocket::Framing03 do
|
4
|
+
class FramingContainer
|
5
|
+
include Rack::WebSocket::Framing03
|
6
|
+
|
7
|
+
def <<(data)
|
8
|
+
@data << data
|
9
|
+
process_data(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(*args); end
|
13
|
+
end
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
@f = FramingContainer.new
|
17
|
+
@f.initialize_framing
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "basic examples" do
|
21
|
+
it "connection close" do
|
22
|
+
@f.should_receive(:message).with(:close, '', '')
|
23
|
+
@f << 0b00000001
|
24
|
+
@f << 0b00000000
|
25
|
+
end
|
26
|
+
|
27
|
+
it "ping" do
|
28
|
+
@f.should_receive(:message).with(:ping, '', '')
|
29
|
+
@f << 0b00000010
|
30
|
+
@f << 0b00000000
|
31
|
+
end
|
32
|
+
|
33
|
+
it "pong" do
|
34
|
+
@f.should_receive(:message).with(:pong, '', '')
|
35
|
+
@f << 0b00000011
|
36
|
+
@f << 0b00000000
|
37
|
+
end
|
38
|
+
|
39
|
+
it "text" do
|
40
|
+
@f.should_receive(:message).with(:text, '', 'foo')
|
41
|
+
@f << 0b00000100
|
42
|
+
@f << 0b00000011
|
43
|
+
@f << 'foo'
|
44
|
+
end
|
45
|
+
|
46
|
+
it "Text in two frames" do
|
47
|
+
@f.should_receive(:message).with(:text, '', 'hello world')
|
48
|
+
@f << 0b10000100
|
49
|
+
@f << 0b00000110
|
50
|
+
@f << "hello "
|
51
|
+
@f << 0b00000000
|
52
|
+
@f << 0b00000101
|
53
|
+
@f << "world"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "2 byte extended payload length text frame" do
|
57
|
+
data = 'a' * 256
|
58
|
+
@f.should_receive(:message).with(:text, '', data)
|
59
|
+
@f << 0b00000100 # Single frame, text
|
60
|
+
@f << 0b01111110 # Length 126 (so read 2 bytes)
|
61
|
+
@f << 0b00000001 # Two bytes in network byte order (256)
|
62
|
+
@f << 0b00000000
|
63
|
+
@f << data
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# These examples are straight from the spec
|
68
|
+
# http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
|
69
|
+
describe "examples from the spec" do
|
70
|
+
it "a single-frame text message" do
|
71
|
+
@f.should_receive(:message).with(:text, '', 'Hello')
|
72
|
+
@f << "\x04\x05Hello"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "a fragmented text message" do
|
76
|
+
@f.should_receive(:message).with(:text, '', 'Hello')
|
77
|
+
@f << "\x84\x03Hel"
|
78
|
+
@f << "\x00\x02lo"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "Ping request and response" do
|
82
|
+
@f.should_receive(:message).with(:ping, '', 'Hello')
|
83
|
+
@f << "\x02\x05Hello"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "256 bytes binary message in a single frame" do
|
87
|
+
data = "a"*256
|
88
|
+
@f.should_receive(:message).with(:binary, '', data)
|
89
|
+
@f << "\x05\x7E\x01\x00" + data
|
90
|
+
end
|
91
|
+
|
92
|
+
it "64KiB binary message in a single frame" do
|
93
|
+
data = "a"*65536
|
94
|
+
@f.should_receive(:message).with(:binary, '', data)
|
95
|
+
@f << "\x05\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "error cases" do
|
100
|
+
it "should raise an exception on continuation frame without preceeding more frame" do
|
101
|
+
lambda {
|
102
|
+
@f << 0b00000000 # Single frame, continuation
|
103
|
+
@f << 0b00000001 # Length 1
|
104
|
+
@f << 'f'
|
105
|
+
}.should raise_error(Rack::WebSocket::WebSocketError, 'Continuation frame not expected')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "Rack::WebSocket::Handler" do
|
4
|
+
before :each do
|
5
|
+
@request = {
|
6
|
+
:port => 80,
|
7
|
+
:method => "GET",
|
8
|
+
:path => "/demo",
|
9
|
+
:headers => {
|
10
|
+
'Host' => 'example.com',
|
11
|
+
'Connection' => 'Upgrade',
|
12
|
+
'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
13
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
14
|
+
'Upgrade' => 'WebSocket',
|
15
|
+
'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
16
|
+
'Origin' => 'http://example.com'
|
17
|
+
},
|
18
|
+
:body => '^n:ds[4U'
|
19
|
+
}
|
20
|
+
@secure_request = @request.merge(:port => 443)
|
21
|
+
|
22
|
+
@response = {
|
23
|
+
:headers => {
|
24
|
+
"Upgrade" => "WebSocket",
|
25
|
+
"Connection" => "Upgrade",
|
26
|
+
"Sec-WebSocket-Location" => "ws://example.com/demo",
|
27
|
+
"Sec-WebSocket-Origin" => "http://example.com",
|
28
|
+
"Sec-WebSocket-Protocol" => "sample"
|
29
|
+
},
|
30
|
+
:body => "8jKS\'y:G*Co,Wxa-"
|
31
|
+
}
|
32
|
+
@secure_response = @response.merge(:headers => @response[:headers].merge('Sec-WebSocket-Location' => "wss://example.com:443/demo"))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should handle good request" do
|
36
|
+
handler(@request).should send_handshake(@response)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should handle good request to secure default port if secure mode is enabled" do
|
40
|
+
handler(@secure_request, true).should send_handshake(@secure_response)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not handle good request to secure default port if secure mode is disabled" do
|
44
|
+
handler(@secure_request, false).should_not send_handshake(@secure_response)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should handle good request on nondefault port" do
|
48
|
+
@request[:port] = 8081
|
49
|
+
@request[:headers]['Host'] = 'example.com:8081'
|
50
|
+
@response[:headers]['Sec-WebSocket-Location'] =
|
51
|
+
'ws://example.com:8081/demo'
|
52
|
+
|
53
|
+
handler(@request).should send_handshake(@response)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should handle good request to secure nondefault port" do
|
57
|
+
@secure_request[:port] = 8081
|
58
|
+
@secure_request[:headers]['Host'] = 'example.com:8081'
|
59
|
+
@secure_response[:headers]['Sec-WebSocket-Location'] = 'wss://example.com:8081/demo'
|
60
|
+
handler(@secure_request, true).should send_handshake(@secure_response)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should handle good request with no protocol" do
|
64
|
+
@request[:headers].delete('Sec-WebSocket-Protocol')
|
65
|
+
@response[:headers].delete("Sec-WebSocket-Protocol")
|
66
|
+
|
67
|
+
handler(@request).should send_handshake(@response)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should handle extra headers by simply ignoring them" do
|
71
|
+
@request[:headers]['EmptyValue'] = ""
|
72
|
+
@request[:headers]['AKey'] = "AValue"
|
73
|
+
|
74
|
+
handler(@request).should send_handshake(@response)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should raise error on HTTP request" do
|
78
|
+
@request[:headers] = {
|
79
|
+
'Host' => 'www.google.com',
|
80
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB6 GTBA',
|
81
|
+
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
82
|
+
'Accept-Language' => 'en-us,en;q=0.5',
|
83
|
+
'Accept-Encoding' => 'gzip,deflate',
|
84
|
+
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
85
|
+
'Keep-Alive' => '300',
|
86
|
+
'Connection' => 'keep-alive',
|
87
|
+
}
|
88
|
+
|
89
|
+
lambda {
|
90
|
+
handler(@request).handshake
|
91
|
+
}.should raise_error(Rack::WebSocket::HandshakeError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should raise error on wrong method" do
|
95
|
+
@request[:method] = 'POST'
|
96
|
+
|
97
|
+
lambda {
|
98
|
+
handler(@request).handshake
|
99
|
+
}.should raise_error(Rack::WebSocket::HandshakeError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should raise error if upgrade header incorrect" do
|
103
|
+
@request[:headers]['Upgrade'] = 'NonWebSocket'
|
104
|
+
|
105
|
+
lambda {
|
106
|
+
handler(@request).handshake
|
107
|
+
}.should raise_error(Rack::WebSocket::HandshakeError)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should raise error if Sec-WebSocket-Protocol is empty" do
|
111
|
+
@request[:headers]['Sec-WebSocket-Protocol'] = ''
|
112
|
+
|
113
|
+
lambda {
|
114
|
+
handler(@request).handshake
|
115
|
+
}.should raise_error(Rack::WebSocket::HandshakeError)
|
116
|
+
end
|
117
|
+
|
118
|
+
%w[Sec-WebSocket-Key1 Sec-WebSocket-Key2].each do |header|
|
119
|
+
it "should raise error if #{header} has zero spaces" do
|
120
|
+
@request[:headers][header] = 'nospaces'
|
121
|
+
|
122
|
+
lambda {
|
123
|
+
handler(@request).handshake
|
124
|
+
}.should raise_error(Rack::WebSocket::HandshakeError, 'Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack')
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
|
129
|
+
@request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
|
130
|
+
|
131
|
+
lambda {
|
132
|
+
handler(@request).handshake
|
133
|
+
}.should raise_error(Rack::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|