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
data/spec/helper.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'em-spec/rspec'
|
4
|
+
require 'pp'
|
5
|
+
require 'em-http'
|
6
|
+
|
7
|
+
require 'em-websocket'
|
8
|
+
|
9
|
+
RSpec.configure do |c|
|
10
|
+
c.mock_with :rspec
|
11
|
+
end
|
12
|
+
|
13
|
+
class FakeWebSocketClient < EM::Connection
|
14
|
+
attr_reader :handshake_response, :packets
|
15
|
+
|
16
|
+
def onopen(&blk); @onopen = blk; end
|
17
|
+
def onclose(&blk); @onclose = blk; end
|
18
|
+
def onerror(&blk); @onerror = blk; end
|
19
|
+
def onmessage(&blk); @onmessage = blk; end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@state = :new
|
23
|
+
@packets = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_data(data)
|
27
|
+
# puts "RECEIVE DATA #{data}"
|
28
|
+
if @state == :new
|
29
|
+
@handshake_response = data
|
30
|
+
@onopen.call if @onopen
|
31
|
+
@state = :open
|
32
|
+
else
|
33
|
+
@onmessage.call(data) if @onmessage
|
34
|
+
@packets << data
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def send(data)
|
39
|
+
send_data("\x00#{data}\xff")
|
40
|
+
end
|
41
|
+
|
42
|
+
def unbind
|
43
|
+
@onclose.call if @onclose
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Draft03FakeWebSocketClient < FakeWebSocketClient
|
48
|
+
def send(application_data)
|
49
|
+
frame = ''
|
50
|
+
opcode = 4 # fake only supports text frames
|
51
|
+
byte1 = opcode # since more, rsv1-3 are 0
|
52
|
+
frame << byte1
|
53
|
+
|
54
|
+
length = application_data.size
|
55
|
+
if length <= 125
|
56
|
+
byte2 = length # since rsv4 is 0
|
57
|
+
frame << byte2
|
58
|
+
elsif length < 65536 # write 2 byte length
|
59
|
+
frame << 126
|
60
|
+
frame << [length].pack('n')
|
61
|
+
else # write 8 byte length
|
62
|
+
frame << 127
|
63
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
64
|
+
end
|
65
|
+
|
66
|
+
frame << application_data
|
67
|
+
|
68
|
+
send_data(frame)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Draft07FakeWebSocketClient < FakeWebSocketClient
|
73
|
+
def send(application_data)
|
74
|
+
frame = ''
|
75
|
+
opcode = 1 # fake only supports text frames
|
76
|
+
byte1 = opcode | 0b10000000 # since more, rsv1-3 are 0
|
77
|
+
frame << byte1
|
78
|
+
|
79
|
+
mask = 0b10000000
|
80
|
+
|
81
|
+
length = application_data.size
|
82
|
+
if length <= 125
|
83
|
+
byte2 = length # since rsv4 is 0
|
84
|
+
frame << (mask | byte2)
|
85
|
+
elsif length < 65536 # write 2 byte length
|
86
|
+
frame << (mask | 126)
|
87
|
+
frame << [length].pack('n')
|
88
|
+
else # write 8 byte length
|
89
|
+
frame << (mask | 127)
|
90
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
91
|
+
end
|
92
|
+
|
93
|
+
frame << EventMachine::WebSocket::MaskedString.create_masked_string(application_data)
|
94
|
+
|
95
|
+
send_data(frame)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# Wrap EM:HttpRequest in a websocket like interface so that it can be used in the specs with the same interface as FakeWebSocketClient
|
101
|
+
class Draft75WebSocketClient
|
102
|
+
def onopen(&blk); @onopen = blk; end
|
103
|
+
def onclose(&blk); @onclose = blk; end
|
104
|
+
def onerror(&blk); @onerror = blk; end
|
105
|
+
def onmessage(&blk); @onmessage = blk; end
|
106
|
+
|
107
|
+
def initialize
|
108
|
+
@ws = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
|
109
|
+
@ws.errback { @onerror.call if @onerror }
|
110
|
+
@ws.callback { @onopen.call if @onopen }
|
111
|
+
@ws.stream { |msg| @onmessage.call(msg) if @onmessage }
|
112
|
+
end
|
113
|
+
|
114
|
+
def send(message)
|
115
|
+
@ws.send(message)
|
116
|
+
end
|
117
|
+
|
118
|
+
def close_connection
|
119
|
+
@ws.close_connection
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def format_request(r)
|
124
|
+
data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
125
|
+
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
126
|
+
data << [header_lines, '', r[:body]].join("\r\n")
|
127
|
+
data
|
128
|
+
end
|
129
|
+
|
130
|
+
def format_response(r)
|
131
|
+
data = r[:protocol] || "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
132
|
+
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
133
|
+
data << [header_lines, '', r[:body]].join("\r\n")
|
134
|
+
data
|
135
|
+
end
|
136
|
+
|
137
|
+
def handler(request, secure = false)
|
138
|
+
connection = Object.new
|
139
|
+
EM::WebSocket::HandlerFactory.build(connection, format_request(request), secure)
|
140
|
+
end
|
141
|
+
|
142
|
+
RSpec::Matchers.define :send_handshake do |response|
|
143
|
+
match do |actual|
|
144
|
+
actual.handshake.lines.sort == format_response(response).lines.sort
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
shared_examples_for "a websocket client" do
|
4
|
+
it "should accept a single-frame binary message" do
|
5
|
+
EM.run do
|
6
|
+
start_server { |server|
|
7
|
+
server.onmessage { |msg, type|
|
8
|
+
msg.should == '\xFF\xFF'
|
9
|
+
type.should == :binary
|
10
|
+
EM.stop
|
11
|
+
}
|
12
|
+
server.onerror {
|
13
|
+
failed
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
options = { :host => '0.0.0.0', :port => 12345, :debug => false }
|
18
|
+
client = EM.connect('0.0.0.0', 12345, EventMachine::WebSocket::ClientConnection, options) do |ws|
|
19
|
+
ws.onopen do
|
20
|
+
ws.send '\xFF\xFF', :binary
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should accept a text message in the same frame as the server handshake response" do
|
27
|
+
EM.run do
|
28
|
+
start_server { |server|
|
29
|
+
server.onopen { server.send 'hello' }
|
30
|
+
server.onerror { failed }
|
31
|
+
}
|
32
|
+
|
33
|
+
options = { :host => '0.0.0.0', :port => 12345, :debug => false }
|
34
|
+
client = EM.connect( options[:host], options[:port], EventMachine::WebSocket::ClientConnection, options) do |ws|
|
35
|
+
ws.onmessage{ |msg, type|
|
36
|
+
msg.should == 'hello'
|
37
|
+
type.should == :text
|
38
|
+
EM.stop
|
39
|
+
}
|
40
|
+
|
41
|
+
EventMachine::add_timer 3 do
|
42
|
+
failed # ran out of time
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# These tests are not specifi to any particular draft of the specification
|
4
|
+
#
|
5
|
+
describe "WebSocket server" do
|
6
|
+
include EM::SpecHelper
|
7
|
+
default_timeout 1
|
8
|
+
|
9
|
+
it "should fail on non WebSocket requests" do
|
10
|
+
em {
|
11
|
+
EventMachine.add_timer(0.1) do
|
12
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
|
13
|
+
http.errback { done }
|
14
|
+
http.callback { fail }
|
15
|
+
end
|
16
|
+
|
17
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) {}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should populate ws.request with appropriate headers" do
|
22
|
+
em {
|
23
|
+
EventMachine.add_timer(0.1) do
|
24
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
25
|
+
http.errback { fail }
|
26
|
+
http.callback {
|
27
|
+
http.response_header.status.should == 101
|
28
|
+
http.close_connection
|
29
|
+
}
|
30
|
+
http.stream { |msg| }
|
31
|
+
end
|
32
|
+
|
33
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
34
|
+
ws.onopen {
|
35
|
+
ws.request["user-agent"].should == "EventMachine HttpClient"
|
36
|
+
ws.request["connection"].should == "Upgrade"
|
37
|
+
ws.request["upgrade"].should == "WebSocket"
|
38
|
+
ws.request["path"].should == "/"
|
39
|
+
ws.request["origin"].should == "127.0.0.1"
|
40
|
+
ws.request["host"].to_s.should == "ws://127.0.0.1:12345"
|
41
|
+
}
|
42
|
+
ws.onclose {
|
43
|
+
ws.state.should == :closed
|
44
|
+
EventMachine.stop
|
45
|
+
}
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should allow sending and retrieving query string args passed in on the connection request." do
|
51
|
+
em {
|
52
|
+
EventMachine.add_timer(0.1) do
|
53
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:query => {'foo' => 'bar', 'baz' => 'qux'}, :timeout => 0)
|
54
|
+
http.errback { fail }
|
55
|
+
http.callback {
|
56
|
+
http.response_header.status.should == 101
|
57
|
+
http.close_connection
|
58
|
+
}
|
59
|
+
http.stream { |msg| }
|
60
|
+
end
|
61
|
+
|
62
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
63
|
+
ws.onopen {
|
64
|
+
path, query = ws.request["path"].split('?')
|
65
|
+
path.should == '/'
|
66
|
+
Hash[*query.split(/&|=/)].should == {"foo"=>"bar", "baz"=>"qux"}
|
67
|
+
ws.request["query"]["foo"].should == "bar"
|
68
|
+
ws.request["query"]["baz"].should == "qux"
|
69
|
+
}
|
70
|
+
ws.onclose {
|
71
|
+
ws.state.should == :closed
|
72
|
+
EventMachine.stop
|
73
|
+
}
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should ws.response['Query'] to empty hash when no query string params passed in connection URI" do
|
79
|
+
em {
|
80
|
+
EventMachine.add_timer(0.1) do
|
81
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
|
82
|
+
http.errback { fail }
|
83
|
+
http.callback {
|
84
|
+
http.response_header.status.should == 101
|
85
|
+
http.close_connection
|
86
|
+
}
|
87
|
+
http.stream { |msg| }
|
88
|
+
end
|
89
|
+
|
90
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
91
|
+
ws.onopen {
|
92
|
+
ws.request["path"].should == "/"
|
93
|
+
ws.request["query"].should == {}
|
94
|
+
}
|
95
|
+
ws.onclose {
|
96
|
+
ws.state.should == :closed
|
97
|
+
EventMachine.stop
|
98
|
+
}
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should raise an exception if frame sent before handshake complete" do
|
104
|
+
em {
|
105
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |c|
|
106
|
+
# We're not using a real client so the handshake will not be sent
|
107
|
+
EM.add_timer(0.1) {
|
108
|
+
lambda {
|
109
|
+
c.send('early message')
|
110
|
+
}.should raise_error('Cannot send data before onopen callback')
|
111
|
+
done
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
client = EM.connect('0.0.0.0', 12345, EM::Connection)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'integration/shared_examples'
|
3
|
+
|
4
|
+
describe "draft03" do
|
5
|
+
include EM::SpecHelper
|
6
|
+
default_timeout 1
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
@request = {
|
10
|
+
:port => 80,
|
11
|
+
:method => "GET",
|
12
|
+
:path => "/demo",
|
13
|
+
:headers => {
|
14
|
+
'Host' => 'example.com',
|
15
|
+
'Connection' => 'Upgrade',
|
16
|
+
'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
17
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
18
|
+
'Upgrade' => 'WebSocket',
|
19
|
+
'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
20
|
+
'Origin' => 'http://example.com',
|
21
|
+
'Sec-WebSocket-Draft' => '3'
|
22
|
+
},
|
23
|
+
:body => '^n:ds[4U'
|
24
|
+
}
|
25
|
+
|
26
|
+
@response = {
|
27
|
+
:headers => {
|
28
|
+
"Upgrade" => "WebSocket",
|
29
|
+
"Connection" => "Upgrade",
|
30
|
+
"Sec-WebSocket-Location" => "ws://example.com/demo",
|
31
|
+
"Sec-WebSocket-Origin" => "http://example.com",
|
32
|
+
"Sec-WebSocket-Protocol" => "sample"
|
33
|
+
},
|
34
|
+
:body => "8jKS\'y:G*Co,Wxa-"
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it_behaves_like "a websocket server" do
|
39
|
+
def start_server
|
40
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
41
|
+
yield ws
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def start_client
|
46
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
47
|
+
client.send_data(format_request(@request))
|
48
|
+
yield client if block_given?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# These examples are straight from the spec
|
53
|
+
# http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
|
54
|
+
describe "examples from the spec" do
|
55
|
+
it "should accept a single-frame text message" do
|
56
|
+
em {
|
57
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
58
|
+
ws.onmessage { |msg|
|
59
|
+
msg.should == 'Hello'
|
60
|
+
done
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
# Create a fake client which sends draft 76 handshake
|
65
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
66
|
+
connection.send_data(format_request(@request))
|
67
|
+
|
68
|
+
# Send frame
|
69
|
+
connection.onopen {
|
70
|
+
connection.send_data("\x04\x05Hello")
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should accept a fragmented text message" do
|
76
|
+
em {
|
77
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
78
|
+
ws.onmessage { |msg|
|
79
|
+
msg.should == 'Hello'
|
80
|
+
done
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
# Create a fake client which sends draft 76 handshake
|
85
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
86
|
+
connection.send_data(format_request(@request))
|
87
|
+
|
88
|
+
# Send frame
|
89
|
+
connection.onopen {
|
90
|
+
connection.send_data("\x84\x03Hel")
|
91
|
+
connection.send_data("\x00\x02lo")
|
92
|
+
}
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should accept a ping request and respond with the same body" do
|
97
|
+
em {
|
98
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
|
99
|
+
|
100
|
+
# Create a fake client which sends draft 76 handshake
|
101
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
102
|
+
connection.send_data(format_request(@request))
|
103
|
+
|
104
|
+
# Send frame
|
105
|
+
connection.onopen {
|
106
|
+
connection.send_data("\x02\x05Hello")
|
107
|
+
}
|
108
|
+
|
109
|
+
connection.onmessage { |frame|
|
110
|
+
next if frame.nil?
|
111
|
+
frame.should == "\x03\x05Hello"
|
112
|
+
done
|
113
|
+
}
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should accept a 256 bytes binary message in a single frame" do
|
118
|
+
em {
|
119
|
+
data = "a" * 256
|
120
|
+
|
121
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
122
|
+
ws.onmessage { |msg|
|
123
|
+
msg.should == data
|
124
|
+
done
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
# Create a fake client which sends draft 76 handshake
|
129
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
130
|
+
connection.send_data(format_request(@request))
|
131
|
+
|
132
|
+
# Send frame
|
133
|
+
connection.onopen {
|
134
|
+
connection.send_data("\x05\x7E\x01\x00" + data)
|
135
|
+
}
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should accept a 64KiB binary message in a single frame" do
|
140
|
+
em {
|
141
|
+
data = "a" * 65536
|
142
|
+
|
143
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
144
|
+
ws.onmessage { |msg|
|
145
|
+
msg.should == data
|
146
|
+
done
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
# Create a fake client which sends draft 76 handshake
|
151
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
152
|
+
connection.send_data(format_request(@request))
|
153
|
+
|
154
|
+
# Send frame
|
155
|
+
connection.onopen {
|
156
|
+
connection.send_data("\x05\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data)
|
157
|
+
}
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "close handling" do
|
163
|
+
it "should respond to a new close frame with a close frame" do
|
164
|
+
em {
|
165
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
|
166
|
+
|
167
|
+
# Create a fake client which sends draft 76 handshake
|
168
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
169
|
+
connection.send_data(format_request(@request))
|
170
|
+
|
171
|
+
# Send close frame
|
172
|
+
connection.onopen {
|
173
|
+
connection.send_data("\x01\x00")
|
174
|
+
}
|
175
|
+
|
176
|
+
# Check that close ack received
|
177
|
+
connection.onmessage { |frame|
|
178
|
+
frame.should == "\x01\x00"
|
179
|
+
done
|
180
|
+
}
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should close the connection on receiving a close acknowlegement" do
|
185
|
+
em {
|
186
|
+
ack_received = false
|
187
|
+
|
188
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
189
|
+
ws.onopen {
|
190
|
+
# 2. Send a close frame
|
191
|
+
EM.next_tick {
|
192
|
+
ws.close_websocket
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
# 1. Create a fake client which sends draft 76 handshake
|
198
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
199
|
+
connection.send_data(format_request(@request))
|
200
|
+
|
201
|
+
# 3. Check that close frame recieved and acknowlege it
|
202
|
+
connection.onmessage { |frame|
|
203
|
+
frame.should == "\x01\x00"
|
204
|
+
ack_received = true
|
205
|
+
connection.send_data("\x01\x00")
|
206
|
+
}
|
207
|
+
|
208
|
+
# 4. Check that connection is closed _after_ the ack
|
209
|
+
connection.onclose {
|
210
|
+
ack_received.should == true
|
211
|
+
done
|
212
|
+
}
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should not allow data frame to be sent after close frame sent" do
|
217
|
+
em {
|
218
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
219
|
+
ws.onopen {
|
220
|
+
# 2. Send a close frame
|
221
|
+
EM.next_tick {
|
222
|
+
ws.close_websocket
|
223
|
+
}
|
224
|
+
|
225
|
+
# 3. Check that exception raised if I attempt to send more data
|
226
|
+
EM.add_timer(0.1) {
|
227
|
+
lambda {
|
228
|
+
ws.send('hello world')
|
229
|
+
}.should raise_error(EM::WebSocket::WebSocketError, 'Cannot send data frame since connection is closing')
|
230
|
+
done
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
# 1. Create a fake client which sends draft 76 handshake
|
236
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
237
|
+
connection.send_data(format_request(@request))
|
238
|
+
}
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should still respond to control frames after close frame sent" do
|
242
|
+
em {
|
243
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
244
|
+
ws.onopen {
|
245
|
+
# 2. Send a close frame
|
246
|
+
EM.next_tick {
|
247
|
+
ws.close_websocket
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
# 1. Create a fake client which sends draft 76 handshake
|
253
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
254
|
+
connection.send_data(format_request(@request))
|
255
|
+
|
256
|
+
connection.onmessage { |frame|
|
257
|
+
if frame == "\x01\x00"
|
258
|
+
# 3. After the close frame is received send a ping frame, but
|
259
|
+
# don't respond with a close ack
|
260
|
+
connection.send_data("\x02\x05Hello")
|
261
|
+
else
|
262
|
+
# 4. Check that the pong is received
|
263
|
+
frame.should == "\x03\x05Hello"
|
264
|
+
done
|
265
|
+
end
|
266
|
+
}
|
267
|
+
}
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "draft05" do
|
4
|
+
include EM::SpecHelper
|
5
|
+
default_timeout 1
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@request = {
|
9
|
+
:port => 80,
|
10
|
+
:method => "GET",
|
11
|
+
:path => "/demo",
|
12
|
+
:headers => {
|
13
|
+
'Host' => 'example.com',
|
14
|
+
'Upgrade' => 'websocket',
|
15
|
+
'Connection' => 'Upgrade',
|
16
|
+
'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==',
|
17
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
18
|
+
'Sec-WebSocket-Origin' => 'http://example.com',
|
19
|
+
'Sec-WebSocket-Version' => '5'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_server
|
25
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
26
|
+
yield ws
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def start_client
|
31
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
32
|
+
client.send_data(format_request(@request))
|
33
|
+
yield client if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should open connection" do
|
37
|
+
em {
|
38
|
+
start_server { |server|
|
39
|
+
server.onopen {
|
40
|
+
server.instance_variable_get(:@handler).class.should == EventMachine::WebSocket::Handler05
|
41
|
+
done
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
start_client
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|