sonixlabs-em-websocket 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|