em-websocket 0.4.0 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +35 -1
- data/Gemfile +6 -0
- data/LICENCE +7 -0
- data/README.md +49 -7
- data/em-websocket.gemspec +2 -8
- data/examples/test.html +3 -1
- data/lib/em-websocket/close03.rb +3 -0
- data/lib/em-websocket/close05.rb +3 -0
- data/lib/em-websocket/close06.rb +3 -0
- data/lib/em-websocket/close75.rb +2 -1
- data/lib/em-websocket/connection.rb +118 -26
- data/lib/em-websocket/framing03.rb +3 -2
- data/lib/em-websocket/framing05.rb +3 -2
- data/lib/em-websocket/framing07.rb +16 -4
- data/lib/em-websocket/framing76.rb +1 -4
- data/lib/em-websocket/handler.rb +31 -3
- data/lib/em-websocket/handler76.rb +2 -0
- data/lib/em-websocket/handshake.rb +23 -4
- data/lib/em-websocket/handshake04.rb +10 -6
- data/lib/em-websocket/handshake75.rb +11 -1
- data/lib/em-websocket/handshake76.rb +4 -0
- data/lib/em-websocket/masking04.rb +1 -5
- data/lib/em-websocket/message_processor_03.rb +6 -3
- data/lib/em-websocket/message_processor_06.rb +30 -9
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +7 -0
- data/spec/helper.rb +67 -52
- data/spec/integration/common_spec.rb +49 -32
- data/spec/integration/draft03_spec.rb +83 -57
- data/spec/integration/draft05_spec.rb +14 -12
- data/spec/integration/draft06_spec.rb +67 -7
- data/spec/integration/draft13_spec.rb +29 -20
- data/spec/integration/draft75_spec.rb +44 -40
- data/spec/integration/draft76_spec.rb +58 -46
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/integration/shared_examples.rb +93 -0
- data/spec/unit/framing_spec.rb +24 -4
- data/spec/unit/handshake_spec.rb +24 -1
- data/spec/unit/masking_spec.rb +2 -0
- metadata +18 -107
@@ -1,4 +1,5 @@
|
|
1
1
|
require "http/parser"
|
2
|
+
require "uri"
|
2
3
|
|
3
4
|
module EventMachine
|
4
5
|
module WebSocket
|
@@ -24,7 +25,7 @@ module EventMachine
|
|
24
25
|
def receive_data(data)
|
25
26
|
@parser << data
|
26
27
|
|
27
|
-
if @headers
|
28
|
+
if defined? @headers
|
28
29
|
process(@headers, @parser.upgrade_data)
|
29
30
|
end
|
30
31
|
rescue HTTP::Parser::Error => e
|
@@ -48,12 +49,12 @@ module EventMachine
|
|
48
49
|
# Returns the request path (excluding any query params)
|
49
50
|
#
|
50
51
|
def path
|
51
|
-
@
|
52
|
+
@path
|
52
53
|
end
|
53
54
|
|
54
55
|
# Returns the query params as a string foo=bar&baz=...
|
55
56
|
def query_string
|
56
|
-
@
|
57
|
+
@query_string
|
57
58
|
end
|
58
59
|
|
59
60
|
def query
|
@@ -66,6 +67,10 @@ module EventMachine
|
|
66
67
|
@headers["origin"] || @headers["sec-websocket-origin"] || nil
|
67
68
|
end
|
68
69
|
|
70
|
+
def secure?
|
71
|
+
@secure
|
72
|
+
end
|
73
|
+
|
69
74
|
private
|
70
75
|
|
71
76
|
def process(headers, remains)
|
@@ -73,13 +78,27 @@ module EventMachine
|
|
73
78
|
raise HandshakeError, "Must be GET request"
|
74
79
|
end
|
75
80
|
|
81
|
+
# Validate request path
|
82
|
+
#
|
83
|
+
# According to http://tools.ietf.org/search/rfc2616#section-5.1.2, an
|
84
|
+
# invalid Request-URI should result in a 400 status code, but
|
85
|
+
# HandshakeError's currently result in a WebSocket abort. It's not
|
86
|
+
# clear which should take precedence, but an abort will do just fine.
|
87
|
+
begin
|
88
|
+
uri = URI.parse(@parser.request_url)
|
89
|
+
@path = uri.path
|
90
|
+
@query_string = uri.query || ""
|
91
|
+
rescue URI::InvalidURIError
|
92
|
+
raise HandshakeError, "Invalid request URI: #{@parser.request_url}"
|
93
|
+
end
|
94
|
+
|
76
95
|
# Validate Upgrade
|
77
96
|
unless @parser.upgrade?
|
78
97
|
raise HandshakeError, "Not an upgrade request"
|
79
98
|
end
|
80
99
|
upgrade = @headers['upgrade']
|
81
100
|
unless upgrade.kind_of?(String) && upgrade.downcase == 'websocket'
|
82
|
-
raise HandshakeError, "Invalid upgrade header: #{upgrade}"
|
101
|
+
raise HandshakeError, "Invalid upgrade header: #{upgrade.inspect}"
|
83
102
|
end
|
84
103
|
|
85
104
|
# Determine version heuristically
|
@@ -10,11 +10,6 @@ module EventMachine
|
|
10
10
|
raise HandshakeError, "sec-websocket-key header is required"
|
11
11
|
end
|
12
12
|
|
13
|
-
# Optional
|
14
|
-
origin = headers['sec-websocket-origin']
|
15
|
-
protocols = headers['sec-websocket-protocol']
|
16
|
-
extensions = headers['sec-websocket-extensions']
|
17
|
-
|
18
13
|
string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
19
14
|
signature = Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
|
20
15
|
|
@@ -22,12 +17,21 @@ module EventMachine
|
|
22
17
|
upgrade << "Upgrade: websocket"
|
23
18
|
upgrade << "Connection: Upgrade"
|
24
19
|
upgrade << "Sec-WebSocket-Accept: #{signature}"
|
20
|
+
if protocol = headers['sec-websocket-protocol']
|
21
|
+
validate_protocol!(protocol)
|
22
|
+
upgrade << "Sec-WebSocket-Protocol: #{protocol}"
|
23
|
+
end
|
25
24
|
|
26
|
-
# TODO: Support sec-websocket-protocol
|
25
|
+
# TODO: Support sec-websocket-protocol selection
|
27
26
|
# TODO: sec-websocket-extensions
|
28
27
|
|
29
28
|
return upgrade.join("\r\n") + "\r\n\r\n"
|
30
29
|
end
|
30
|
+
|
31
|
+
def self.validate_protocol!(protocol)
|
32
|
+
raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
|
33
|
+
# TODO: Validate characters
|
34
|
+
end
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -9,10 +9,20 @@ module EventMachine
|
|
9
9
|
upgrade << "Upgrade: WebSocket\r\n"
|
10
10
|
upgrade << "Connection: Upgrade\r\n"
|
11
11
|
upgrade << "WebSocket-Origin: #{headers['origin']}\r\n"
|
12
|
-
upgrade << "WebSocket-Location: #{location}\r\n
|
12
|
+
upgrade << "WebSocket-Location: #{location}\r\n"
|
13
|
+
if protocol = headers['sec-websocket-protocol']
|
14
|
+
validate_protocol!(protocol)
|
15
|
+
upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
|
16
|
+
end
|
17
|
+
upgrade << "\r\n"
|
13
18
|
|
14
19
|
return upgrade
|
15
20
|
end
|
21
|
+
|
22
|
+
def self.validate_protocol!(protocol)
|
23
|
+
raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
|
24
|
+
# TODO: Validate characters
|
25
|
+
end
|
16
26
|
end
|
17
27
|
end
|
18
28
|
end
|
@@ -15,12 +15,8 @@ module EventMachine
|
|
15
15
|
@masking_key = nil
|
16
16
|
end
|
17
17
|
|
18
|
-
def slice_mask
|
19
|
-
slice!(0, 4)
|
20
|
-
end
|
21
|
-
|
22
18
|
def getbyte(index)
|
23
|
-
if @masking_key
|
19
|
+
if defined?(@masking_key) && @masking_key
|
24
20
|
masked_char = super
|
25
21
|
masked_char ? masked_char ^ @masking_key.getbyte(index % 4) : nil
|
26
22
|
else
|
@@ -6,17 +6,20 @@ module EventMachine
|
|
6
6
|
def message(message_type, extension_data, application_data)
|
7
7
|
case message_type
|
8
8
|
when :close
|
9
|
+
@close_info = {
|
10
|
+
:code => 1005,
|
11
|
+
:reason => "",
|
12
|
+
:was_clean => true,
|
13
|
+
}
|
9
14
|
if @state == :closing
|
10
15
|
# TODO: Check that message body matches sent data
|
11
16
|
# We can close connection immediately since there is no more data
|
12
17
|
# is allowed to be sent or received on this connection
|
13
18
|
@connection.close_connection
|
14
|
-
@state = :closed
|
15
19
|
else
|
16
20
|
# Acknowlege close
|
17
21
|
# The connection is considered closed
|
18
22
|
send_frame(:close, application_data)
|
19
|
-
@state = :closed
|
20
23
|
@connection.close_connection_after_writing
|
21
24
|
end
|
22
25
|
when :ping
|
@@ -31,7 +34,7 @@ module EventMachine
|
|
31
34
|
end
|
32
35
|
@connection.trigger_on_message(application_data)
|
33
36
|
when :binary
|
34
|
-
@connection.
|
37
|
+
@connection.trigger_on_binary(application_data)
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
@@ -19,32 +19,53 @@ module EventMachine
|
|
19
19
|
|
20
20
|
debug [:close_frame_received, status_code, application_data]
|
21
21
|
|
22
|
+
@close_info = {
|
23
|
+
:code => status_code || 1005,
|
24
|
+
:reason => application_data,
|
25
|
+
:was_clean => true,
|
26
|
+
}
|
27
|
+
|
22
28
|
if @state == :closing
|
23
29
|
# We can close connection immediately since no more data may be
|
24
30
|
# sent or received on this connection
|
25
31
|
@connection.close_connection
|
26
|
-
|
27
|
-
|
28
|
-
# Acknowlege close
|
32
|
+
elsif @state == :connected
|
33
|
+
# Acknowlege close & echo status back to client
|
29
34
|
# The connection is considered closed
|
30
|
-
|
31
|
-
|
35
|
+
close_data = [status_code || 1000].pack('n')
|
36
|
+
send_frame(:close, close_data)
|
32
37
|
@connection.close_connection_after_writing
|
33
|
-
# TODO: Send close status code and body to app code
|
34
38
|
end
|
35
39
|
when :ping
|
36
|
-
#
|
37
|
-
|
40
|
+
# There are a couple of protections here against malicious/broken WebSocket abusing ping frames.
|
41
|
+
#
|
42
|
+
# 1. Delay 200ms before replying. This reduces the number of pings from WebSocket clients behaving as
|
43
|
+
# `for (;;) { send_ping(conn); rcv_pong(conn); }`. The spec says we "SHOULD respond with Pong frame as soon
|
44
|
+
# as is practical".
|
45
|
+
# 2. Reply at most every 200ms. This reduces the number of pong frames sent to WebSocket clients behaving as
|
46
|
+
# `for (;;) { send_ping(conn); }`. The spec says "If an endpoint receives a Ping frame and has not yet sent
|
47
|
+
# Pong frame(s) in response to previous Ping frame(s), the endpoint MAY elect to send a Pong frame for only
|
48
|
+
# the most recently processed Ping frame."
|
49
|
+
@most_recent_pong_application_data = application_data
|
50
|
+
if @pong_timer == nil then
|
51
|
+
@pong_timer = EventMachine.add_timer(0.2) do
|
52
|
+
@pong_timer = nil
|
53
|
+
send_frame(:pong, @most_recent_pong_application_data)
|
54
|
+
end
|
55
|
+
end
|
38
56
|
@connection.trigger_on_ping(application_data)
|
39
57
|
when :pong
|
40
58
|
@connection.trigger_on_pong(application_data)
|
41
59
|
when :text
|
42
60
|
if application_data.respond_to?(:force_encoding)
|
43
61
|
application_data.force_encoding("UTF-8")
|
62
|
+
unless application_data.valid_encoding?
|
63
|
+
raise InvalidDataError, "Invalid UTF8 data"
|
64
|
+
end
|
44
65
|
end
|
45
66
|
@connection.trigger_on_message(application_data)
|
46
67
|
when :binary
|
47
|
-
@connection.
|
68
|
+
@connection.trigger_on_binary(application_data)
|
48
69
|
end
|
49
70
|
end
|
50
71
|
|
data/lib/em-websocket/version.rb
CHANGED
@@ -2,8 +2,11 @@ module EventMachine
|
|
2
2
|
module WebSocket
|
3
3
|
class << self
|
4
4
|
attr_accessor :max_frame_size
|
5
|
+
attr_accessor :close_timeout
|
5
6
|
end
|
6
7
|
@max_frame_size = 10 * 1024 * 1024 # 10MB
|
8
|
+
# Connections are given 60s to close after being sent a close handshake
|
9
|
+
@close_timeout = 60
|
7
10
|
|
8
11
|
# All errors raised by em-websocket should descend from this class
|
9
12
|
class WebSocketError < RuntimeError; end
|
@@ -17,6 +20,10 @@ module EventMachine
|
|
17
20
|
def code; 1002; end
|
18
21
|
end
|
19
22
|
|
23
|
+
class InvalidDataError < WSProtocolError
|
24
|
+
def code; 1007; end
|
25
|
+
end
|
26
|
+
|
20
27
|
# 1009: Message too big to process
|
21
28
|
class WSMessageTooBigError < WSProtocolError
|
22
29
|
def code; 1009; end
|
data/spec/helper.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'rspec'
|
3
5
|
require 'em-spec/rspec'
|
4
|
-
require 'pp'
|
5
6
|
require 'em-http'
|
6
7
|
|
7
8
|
require 'em-websocket'
|
9
|
+
require 'em-websocket-client'
|
10
|
+
|
11
|
+
require 'integration/shared_examples'
|
12
|
+
require 'integration/gte_03_examples'
|
8
13
|
|
9
14
|
RSpec.configure do |c|
|
10
15
|
c.mock_with :rspec
|
@@ -27,75 +32,80 @@ class FakeWebSocketClient < EM::Connection
|
|
27
32
|
# puts "RECEIVE DATA #{data}"
|
28
33
|
if @state == :new
|
29
34
|
@handshake_response = data
|
30
|
-
@onopen.call if @onopen
|
35
|
+
@onopen.call if defined? @onopen
|
31
36
|
@state = :open
|
32
37
|
else
|
33
|
-
@onmessage.call(data) if @onmessage
|
38
|
+
@onmessage.call(data) if defined? @onmessage
|
34
39
|
@packets << data
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
38
|
-
def send(
|
39
|
-
|
43
|
+
def send(application_data)
|
44
|
+
send_frame(:text, application_data)
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_frame(type, application_data)
|
48
|
+
send_data construct_frame(type, application_data)
|
40
49
|
end
|
41
50
|
|
42
51
|
def unbind
|
43
|
-
@onclose.call if @onclose
|
52
|
+
@onclose.call if defined? @onclose
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def construct_frame(type, data)
|
58
|
+
"\x00#{data}\xff"
|
44
59
|
end
|
45
60
|
end
|
46
61
|
|
47
62
|
class Draft03FakeWebSocketClient < FakeWebSocketClient
|
48
|
-
|
49
|
-
frame = ''
|
50
|
-
opcode = 4 # fake only supports text frames
|
51
|
-
byte1 = opcode # since more, rsv1-3 are 0
|
52
|
-
frame << byte1
|
63
|
+
private
|
53
64
|
|
54
|
-
|
65
|
+
def construct_frame(type, data)
|
66
|
+
frame = ""
|
67
|
+
frame << EM::WebSocket::Framing03::FRAME_TYPES[type]
|
68
|
+
frame << encoded_length(data.size)
|
69
|
+
frame << data
|
70
|
+
end
|
71
|
+
|
72
|
+
def encoded_length(length)
|
55
73
|
if length <= 125
|
56
|
-
|
57
|
-
frame << byte2
|
74
|
+
[length].pack('C') # since rsv4 is 0
|
58
75
|
elsif length < 65536 # write 2 byte length
|
59
|
-
|
60
|
-
frame << [length].pack('n')
|
76
|
+
"\126#{[length].pack('n')}"
|
61
77
|
else # write 8 byte length
|
62
|
-
|
63
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
78
|
+
"\127#{[length >> 32, length & 0xFFFFFFFF].pack("NN")}"
|
64
79
|
end
|
65
|
-
|
66
|
-
frame << application_data
|
67
|
-
|
68
|
-
send_data(frame)
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
72
|
-
class
|
73
|
-
|
74
|
-
frame = ''
|
75
|
-
opcode = 1 # fake only supports text frames
|
76
|
-
byte1 = opcode | 0b10000000 # since more, rsv1-3 are 0
|
77
|
-
frame << byte1
|
83
|
+
class Draft05FakeWebSocketClient < Draft03FakeWebSocketClient
|
84
|
+
private
|
78
85
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
frame << 127
|
88
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
89
|
-
end
|
86
|
+
def construct_frame(type, data)
|
87
|
+
frame = ""
|
88
|
+
frame << "\x00\x00\x00\x00" # Mask with nothing for simplicity
|
89
|
+
frame << (EM::WebSocket::Framing05::FRAME_TYPES[type] | 0b10000000)
|
90
|
+
frame << encoded_length(data.size)
|
91
|
+
frame << data
|
92
|
+
end
|
93
|
+
end
|
90
94
|
|
91
|
-
|
95
|
+
class Draft07FakeWebSocketClient < Draft05FakeWebSocketClient
|
96
|
+
private
|
92
97
|
|
93
|
-
|
98
|
+
def construct_frame(type, data)
|
99
|
+
frame = ""
|
100
|
+
frame << (EM::WebSocket::Framing07::FRAME_TYPES[type] | 0b10000000)
|
101
|
+
# Should probably mask the data, but I get away without bothering since
|
102
|
+
# the server doesn't enforce that incoming frames are masked
|
103
|
+
frame << encoded_length(data.size)
|
104
|
+
frame << data
|
94
105
|
end
|
95
106
|
end
|
96
107
|
|
97
|
-
|
98
|
-
# Wrap EM:HttpRequest in a websocket like interface so that it can be used in the specs with the same interface as FakeWebSocketClient
|
108
|
+
# Wrapper around em-websocket-client
|
99
109
|
class Draft75WebSocketClient
|
100
110
|
def onopen(&blk); @onopen = blk; end
|
101
111
|
def onclose(&blk); @onclose = blk; end
|
@@ -103,18 +113,17 @@ class Draft75WebSocketClient
|
|
103
113
|
def onmessage(&blk); @onmessage = blk; end
|
104
114
|
|
105
115
|
def initialize
|
106
|
-
@ws = EventMachine::
|
107
|
-
|
108
|
-
|
109
|
-
}
|
110
|
-
@ws.
|
111
|
-
@ws.
|
112
|
-
@ws.
|
113
|
-
@ws.disconnect { @onclose.call if @onclose }
|
116
|
+
@ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/',
|
117
|
+
:version => 75,
|
118
|
+
:origin => 'http://example.com')
|
119
|
+
@ws.errback { |err| @onerror.call if defined? @onerror }
|
120
|
+
@ws.callback { @onopen.call if defined? @onopen }
|
121
|
+
@ws.stream { |msg| @onmessage.call(msg) if defined? @onmessage }
|
122
|
+
@ws.disconnect { @onclose.call if defined? @onclose }
|
114
123
|
end
|
115
124
|
|
116
125
|
def send(message)
|
117
|
-
@ws.
|
126
|
+
@ws.send_msg(message)
|
118
127
|
end
|
119
128
|
|
120
129
|
def close_connection
|
@@ -122,6 +131,12 @@ class Draft75WebSocketClient
|
|
122
131
|
end
|
123
132
|
end
|
124
133
|
|
134
|
+
def start_server(opts = {})
|
135
|
+
EM::WebSocket.run({:host => "0.0.0.0", :port => 12345}.merge(opts)) { |ws|
|
136
|
+
yield ws if block_given?
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
125
140
|
def format_request(r)
|
126
141
|
data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
127
142
|
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
@@ -14,32 +14,29 @@ describe "WebSocket server" do
|
|
14
14
|
http.callback { fail }
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
start_server
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should expose the WebSocket request headers, path and query params" do
|
22
22
|
em {
|
23
23
|
EM.add_timer(0.1) do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}
|
30
|
-
http.stream { |msg| }
|
24
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/',
|
25
|
+
:origin => 'http://example.com')
|
26
|
+
ws.errback { fail }
|
27
|
+
ws.callback { ws.close_connection }
|
28
|
+
ws.stream { |msg| }
|
31
29
|
end
|
32
30
|
|
33
|
-
|
31
|
+
start_server do |ws|
|
34
32
|
ws.onopen { |handshake|
|
35
33
|
headers = handshake.headers
|
36
|
-
headers["User-Agent"].should == "EventMachine HttpClient"
|
37
34
|
headers["Connection"].should == "Upgrade"
|
38
|
-
headers["Upgrade"].should == "
|
35
|
+
headers["Upgrade"].should == "websocket"
|
39
36
|
headers["Host"].to_s.should == "127.0.0.1:12345"
|
40
37
|
handshake.path.should == "/"
|
41
38
|
handshake.query.should == {}
|
42
|
-
handshake.origin.should ==
|
39
|
+
handshake.origin.should == 'http://example.com'
|
43
40
|
}
|
44
41
|
ws.onclose {
|
45
42
|
ws.state.should == :closed
|
@@ -52,19 +49,15 @@ describe "WebSocket server" do
|
|
52
49
|
it "should expose the WebSocket path and query params when nonempty" do
|
53
50
|
em {
|
54
51
|
EM.add_timer(0.1) do
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
http.errback { fail }
|
60
|
-
http.callback {
|
61
|
-
http.response_header.status.should == 101
|
62
|
-
http.close_connection
|
52
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/hello?foo=bar&baz=qux')
|
53
|
+
ws.errback { fail }
|
54
|
+
ws.callback {
|
55
|
+
ws.close_connection
|
63
56
|
}
|
64
|
-
|
57
|
+
ws.stream { |msg| }
|
65
58
|
end
|
66
59
|
|
67
|
-
|
60
|
+
start_server do |ws|
|
68
61
|
ws.onopen { |handshake|
|
69
62
|
handshake.path.should == '/hello'
|
70
63
|
handshake.query_string.split('&').sort.
|
@@ -82,7 +75,7 @@ describe "WebSocket server" do
|
|
82
75
|
it "should raise an exception if frame sent before handshake complete" do
|
83
76
|
em {
|
84
77
|
# 1. Start WebSocket server
|
85
|
-
|
78
|
+
start_server { |ws|
|
86
79
|
# 3. Try to send a message to the socket
|
87
80
|
lambda {
|
88
81
|
ws.send('early message')
|
@@ -98,18 +91,15 @@ describe "WebSocket server" do
|
|
98
91
|
it "should allow the server to be started inside an existing EM" do
|
99
92
|
em {
|
100
93
|
EM.add_timer(0.1) do
|
101
|
-
http = EM::HttpRequest.new('
|
102
|
-
http.errback {
|
103
|
-
http.callback {
|
104
|
-
http.response_header.status.should == 101
|
105
|
-
http.close_connection
|
106
|
-
}
|
107
|
-
http.stream { |msg| }
|
94
|
+
http = EM::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
|
95
|
+
http.errback { |e| done }
|
96
|
+
http.callback { fail }
|
108
97
|
end
|
109
98
|
|
110
|
-
|
99
|
+
start_server do |ws|
|
111
100
|
ws.onopen { |handshake|
|
112
|
-
handshake.headers
|
101
|
+
headers = handshake.headers
|
102
|
+
headers["Host"].to_s.should == "127.0.0.1:12345"
|
113
103
|
}
|
114
104
|
ws.onclose {
|
115
105
|
ws.state.should == :closed
|
@@ -118,4 +108,31 @@ describe "WebSocket server" do
|
|
118
108
|
end
|
119
109
|
}
|
120
110
|
end
|
111
|
+
|
112
|
+
context "outbound limit set" do
|
113
|
+
it "should close the connection if the limit is reached" do
|
114
|
+
em {
|
115
|
+
start_server(:outbound_limit => 150) do |ws|
|
116
|
+
# Increase the message size by one on each loop
|
117
|
+
ws.onmessage{|msg| ws.send(msg + "x") }
|
118
|
+
ws.onclose{|status|
|
119
|
+
status[:code].should == 1006 # Unclean
|
120
|
+
status[:was_clean].should be false
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
EM.add_timer(0.1) do
|
125
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/')
|
126
|
+
ws.callback { ws.send_msg "hello" }
|
127
|
+
ws.disconnect { done } # Server closed the connection
|
128
|
+
ws.stream { |msg|
|
129
|
+
# minus frame size ? (getting 146 max here)
|
130
|
+
msg.data.size.should <= 150
|
131
|
+
# Return back the message
|
132
|
+
ws.send_msg(msg.data)
|
133
|
+
}
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
121
138
|
end
|