em-websocket 0.4.0 → 0.5.3
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.
- 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
|