em-websocket 0.5.0 → 0.5.1
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 +19 -0
- data/Gemfile +6 -0
- data/README.md +3 -2
- data/lib/em-websocket/close03.rb +1 -0
- data/lib/em-websocket/close05.rb +1 -0
- data/lib/em-websocket/close06.rb +1 -0
- data/lib/em-websocket/connection.rb +39 -8
- data/lib/em-websocket/framing03.rb +1 -1
- data/lib/em-websocket/framing05.rb +1 -1
- data/lib/em-websocket/framing07.rb +14 -3
- data/lib/em-websocket/framing76.rb +1 -4
- data/lib/em-websocket/handler.rb +24 -2
- data/lib/em-websocket/handshake.rb +18 -3
- data/lib/em-websocket/handshake76.rb +4 -0
- data/lib/em-websocket/message_processor_03.rb +1 -1
- data/lib/em-websocket/message_processor_06.rb +1 -1
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +3 -0
- data/spec/helper.rb +16 -7
- data/spec/integration/common_spec.rb +17 -33
- data/spec/integration/draft03_spec.rb +8 -9
- data/spec/integration/draft05_spec.rb +4 -7
- data/spec/integration/draft06_spec.rb +4 -7
- data/spec/integration/draft13_spec.rb +4 -7
- data/spec/integration/draft75_spec.rb +18 -27
- data/spec/integration/draft76_spec.rb +1 -8
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/unit/framing_spec.rb +21 -3
- data/spec/unit/handshake_spec.rb +24 -1
- metadata +16 -106
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 623f09b2677a73143c0f625ba3b516a00219103e
|
4
|
+
data.tar.gz: 0ebcdd8490811e0b69a64262b2a63daeb8d6aa14
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aad00490beaf8c9ae3606dc506d111c7d60c1f83c6b08c93ebbafd9d877f3c7a7ec2273cbb0a3d97f59d7fb749e4a6f157cdc1a53f4bab88c21cb6eb99605435
|
7
|
+
data.tar.gz: 002ff0471519223522a43a3e865272af61c033960d4a74506080b551d8b01f3cc73471c4b19d677b7c3f90c76b8022c34aa5fbb0b4a3b348b37102b1a3ad597e
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
= Changelog
|
2
2
|
|
3
|
+
== 0.5.1 / 2014-04-23
|
4
|
+
|
5
|
+
- new features:
|
6
|
+
- Support for receiving binary messages
|
7
|
+
|
8
|
+
- changed:
|
9
|
+
- Allow additional close codes to be sent by apps
|
10
|
+
- Raise better errors on missing Sec-WebSocket-Key2
|
11
|
+
- Updated http_parser.rb dependency to 0.6.0
|
12
|
+
|
13
|
+
- bug fixes:
|
14
|
+
- Abort if HTTP request URI is invalid
|
15
|
+
- Force close connections that have been sent a close handshake after a timeout
|
16
|
+
|
17
|
+
- improved spec compliance on:
|
18
|
+
- Missing continuation frames
|
19
|
+
- Fragmented control frames
|
20
|
+
- Close behaviour after protocol errors
|
21
|
+
|
3
22
|
== 0.5.0 / 2013-03-05
|
4
23
|
|
5
24
|
- new features:
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# EM-WebSocket
|
2
2
|
|
3
|
-
|
3
|
+
[](http://rubygems.org/gems/em-websocket)
|
4
|
+
[](https://github.com/igrigorik/ga-beacon)
|
4
5
|
|
5
|
-
|
6
|
+
EventMachine based, async, Ruby WebSocket server. Take a look at examples directory, or check out the blog post: [Ruby & Websockets: TCP for the Web](http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/).
|
6
7
|
|
7
8
|
## Simple server example
|
8
9
|
|
data/lib/em-websocket/close03.rb
CHANGED
data/lib/em-websocket/close05.rb
CHANGED
data/lib/em-websocket/close06.rb
CHANGED
@@ -10,12 +10,16 @@ module EventMachine
|
|
10
10
|
def onclose(&blk); @onclose = blk; end
|
11
11
|
def onerror(&blk); @onerror = blk; end
|
12
12
|
def onmessage(&blk); @onmessage = blk; end
|
13
|
+
def onbinary(&blk); @onbinary = blk; end
|
13
14
|
def onping(&blk); @onping = blk; end
|
14
15
|
def onpong(&blk); @onpong = blk; end
|
15
16
|
|
16
17
|
def trigger_on_message(msg)
|
17
18
|
@onmessage.call(msg) if defined? @onmessage
|
18
19
|
end
|
20
|
+
def trigger_on_binary(msg)
|
21
|
+
@onbinary.call(msg) if defined? @onbinary
|
22
|
+
end
|
19
23
|
def trigger_on_open(handshake)
|
20
24
|
@onopen.call(handshake) if defined? @onopen
|
21
25
|
end
|
@@ -40,6 +44,7 @@ module EventMachine
|
|
40
44
|
@secure = options[:secure] || false
|
41
45
|
@secure_proxy = options[:secure_proxy] || false
|
42
46
|
@tls_options = options[:tls_options] || {}
|
47
|
+
@close_timeout = options[:close_timeout]
|
43
48
|
|
44
49
|
@handler = nil
|
45
50
|
|
@@ -72,10 +77,6 @@ module EventMachine
|
|
72
77
|
else
|
73
78
|
dispatch(data)
|
74
79
|
end
|
75
|
-
rescue WSProtocolError => e
|
76
|
-
debug [:error, e]
|
77
|
-
trigger_on_error(e)
|
78
|
-
close_websocket_private(e.code, e.message)
|
79
80
|
rescue => e
|
80
81
|
debug [:error, e]
|
81
82
|
|
@@ -247,6 +248,10 @@ module EventMachine
|
|
247
248
|
defined?(@max_frame_size) ? @max_frame_size : WebSocket.max_frame_size
|
248
249
|
end
|
249
250
|
|
251
|
+
def close_timeout
|
252
|
+
@close_timeout || WebSocket.close_timeout
|
253
|
+
end
|
254
|
+
|
250
255
|
private
|
251
256
|
|
252
257
|
# As definited in draft 06 7.2.2, some failures require that the server
|
@@ -265,13 +270,39 @@ module EventMachine
|
|
265
270
|
end
|
266
271
|
end
|
267
272
|
|
268
|
-
#
|
273
|
+
# Allow applications to close with 1000, 1003, 1008, 1011, 3xxx or 4xxx.
|
274
|
+
#
|
275
|
+
# em-websocket uses a few other codes internally which should not be
|
276
|
+
# used by applications
|
277
|
+
#
|
278
|
+
# Browsers generally allow connections to be closed with code 1000,
|
279
|
+
# 3xxx, and 4xxx. em-websocket allows closing with a few other codes
|
280
|
+
# which seem reasonable (for discussion see
|
281
|
+
# https://github.com/igrigorik/em-websocket/issues/98)
|
282
|
+
#
|
283
|
+
# Usage from the rfc:
|
284
|
+
#
|
285
|
+
# 1000 indicates a normal closure
|
286
|
+
#
|
287
|
+
# 1003 indicates that an endpoint is terminating the connection
|
288
|
+
# because it has received a type of data it cannot accept
|
289
|
+
#
|
290
|
+
# 1008 indicates that an endpoint is terminating the connection because
|
291
|
+
# it has received a message that violates its policy
|
292
|
+
#
|
293
|
+
# 1011 indicates that a server is terminating the connection because it
|
294
|
+
# encountered an unexpected condition that prevented it from fulfilling
|
295
|
+
# the request
|
296
|
+
#
|
297
|
+
# Status codes in the range 3000-3999 are reserved for use by libraries,
|
298
|
+
# frameworks, and applications
|
299
|
+
#
|
300
|
+
# Status codes in the range 4000-4999 are reserved for private use and
|
301
|
+
# thus can't be registered
|
269
302
|
#
|
270
|
-
# This is consistent with the spec and what browsers have implemented
|
271
|
-
# Frameworks should use 3xxx while applications should use 4xxx
|
272
303
|
def acceptable_close_code?(code)
|
273
304
|
case code
|
274
|
-
when 1000, (3000..4999)
|
305
|
+
when 1000, 1003, 1008, 1011, (3000..4999)
|
275
306
|
true
|
276
307
|
else
|
277
308
|
false
|
@@ -10,7 +10,7 @@ module EventMachine
|
|
10
10
|
@frame_type = nil
|
11
11
|
end
|
12
12
|
|
13
|
-
def process_data
|
13
|
+
def process_data
|
14
14
|
error = false
|
15
15
|
|
16
16
|
while !error && @data.size >= 2
|
@@ -87,8 +87,19 @@ module EventMachine
|
|
87
87
|
|
88
88
|
frame_type = opcode_to_type(opcode)
|
89
89
|
|
90
|
-
if frame_type == :continuation
|
91
|
-
|
90
|
+
if frame_type == :continuation
|
91
|
+
if !@frame_type
|
92
|
+
raise WSProtocolError, 'Continuation frame not expected'
|
93
|
+
end
|
94
|
+
else # Not a continuation frame
|
95
|
+
if @frame_type && data_frame?(frame_type)
|
96
|
+
raise WSProtocolError, "Continuation frame expected"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Validate that control frames are not fragmented
|
101
|
+
if !fin && !data_frame?(frame_type)
|
102
|
+
raise WSProtocolError, 'Control frames must not be fragmented'
|
92
103
|
end
|
93
104
|
|
94
105
|
if !fin
|
@@ -7,7 +7,7 @@ module EventMachine
|
|
7
7
|
@data = ''
|
8
8
|
end
|
9
9
|
|
10
|
-
def process_data
|
10
|
+
def process_data
|
11
11
|
debug [:message, @data]
|
12
12
|
|
13
13
|
# This algorithm comes straight from the spec
|
@@ -71,9 +71,6 @@ module EventMachine
|
|
71
71
|
raise WSMessageTooBigError, "Frame length too long (#{@data.size} bytes)"
|
72
72
|
end
|
73
73
|
|
74
|
-
# Optimization to avoid calling slice! unnecessarily
|
75
|
-
error = true and next unless newdata =~ /\xff/
|
76
|
-
|
77
74
|
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
78
75
|
if msg
|
79
76
|
msg.gsub!(/\A\x00|\xff\z/, '')
|
data/lib/em-websocket/handler.rb
CHANGED
@@ -38,27 +38,49 @@ module EventMachine
|
|
38
38
|
@connection = connection
|
39
39
|
@debug = debug
|
40
40
|
@state = :connected
|
41
|
+
@close_timer = nil
|
41
42
|
initialize_framing
|
42
43
|
end
|
43
44
|
|
44
45
|
def receive_data(data)
|
45
46
|
@data << data
|
46
|
-
process_data
|
47
|
+
process_data
|
48
|
+
rescue WSProtocolError => e
|
49
|
+
fail_websocket(e)
|
47
50
|
end
|
48
51
|
|
49
52
|
def close_websocket(code, body)
|
50
53
|
# Implemented in subclass
|
51
54
|
end
|
52
55
|
|
56
|
+
# Used to avoid un-acked and unclosed remaining open indefinitely
|
57
|
+
def start_close_timeout
|
58
|
+
@close_timer = EM::Timer.new(@connection.close_timeout) {
|
59
|
+
@connection.close_connection
|
60
|
+
e = WSProtocolError.new("Close handshake un-acked after #{@connection.close_timeout}s, closing tcp connection")
|
61
|
+
@connection.trigger_on_error(e)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
# This corresponds to "Fail the WebSocket Connection" in the spec.
|
66
|
+
def fail_websocket(e)
|
67
|
+
debug [:error, e]
|
68
|
+
close_websocket(e.code, e.message)
|
69
|
+
@connection.close_connection_after_writing
|
70
|
+
@connection.trigger_on_error(e)
|
71
|
+
end
|
72
|
+
|
53
73
|
def unbind
|
54
74
|
@state = :closed
|
55
75
|
|
76
|
+
@close_timer.cancel if @close_timer
|
77
|
+
|
56
78
|
@close_info = defined?(@close_info) ? @close_info : {
|
57
79
|
:code => 1006,
|
58
80
|
:was_clean => false,
|
59
81
|
}
|
60
82
|
|
61
|
-
@connection.trigger_on_close(@close_info
|
83
|
+
@connection.trigger_on_close(@close_info)
|
62
84
|
end
|
63
85
|
|
64
86
|
def ping
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "http/parser"
|
2
|
+
require "uri"
|
2
3
|
|
3
4
|
module EventMachine
|
4
5
|
module WebSocket
|
@@ -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
|
@@ -77,13 +78,27 @@ module EventMachine
|
|
77
78
|
raise HandshakeError, "Must be GET request"
|
78
79
|
end
|
79
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
|
+
|
80
95
|
# Validate Upgrade
|
81
96
|
unless @parser.upgrade?
|
82
97
|
raise HandshakeError, "Not an upgrade request"
|
83
98
|
end
|
84
99
|
upgrade = @headers['upgrade']
|
85
100
|
unless upgrade.kind_of?(String) && upgrade.downcase == 'websocket'
|
86
|
-
raise HandshakeError, "Invalid upgrade header: #{upgrade}"
|
101
|
+
raise HandshakeError, "Invalid upgrade header: #{upgrade.inspect}"
|
87
102
|
end
|
88
103
|
|
89
104
|
# Determine version heuristically
|
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
|
data/spec/helper.rb
CHANGED
@@ -6,6 +6,10 @@ require 'em-spec/rspec'
|
|
6
6
|
require 'em-http'
|
7
7
|
|
8
8
|
require 'em-websocket'
|
9
|
+
require 'em-websocket-client'
|
10
|
+
|
11
|
+
require 'integration/shared_examples'
|
12
|
+
require 'integration/gte_03_examples'
|
9
13
|
|
10
14
|
RSpec.configure do |c|
|
11
15
|
c.mock_with :rspec
|
@@ -101,7 +105,7 @@ class Draft07FakeWebSocketClient < Draft05FakeWebSocketClient
|
|
101
105
|
end
|
102
106
|
end
|
103
107
|
|
104
|
-
#
|
108
|
+
# Wrapper around em-websocket-client
|
105
109
|
class Draft75WebSocketClient
|
106
110
|
def onopen(&blk); @onopen = blk; end
|
107
111
|
def onclose(&blk); @onclose = blk; end
|
@@ -109,18 +113,17 @@ class Draft75WebSocketClient
|
|
109
113
|
def onmessage(&blk); @onmessage = blk; end
|
110
114
|
|
111
115
|
def initialize
|
112
|
-
@ws = EventMachine::
|
113
|
-
|
114
|
-
|
115
|
-
}
|
116
|
-
@ws.errback { @onerror.call if defined? @onerror }
|
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 }
|
117
120
|
@ws.callback { @onopen.call if defined? @onopen }
|
118
121
|
@ws.stream { |msg| @onmessage.call(msg) if defined? @onmessage }
|
119
122
|
@ws.disconnect { @onclose.call if defined? @onclose }
|
120
123
|
end
|
121
124
|
|
122
125
|
def send(message)
|
123
|
-
@ws.
|
126
|
+
@ws.send_msg(message)
|
124
127
|
end
|
125
128
|
|
126
129
|
def close_connection
|
@@ -128,6 +131,12 @@ class Draft75WebSocketClient
|
|
128
131
|
end
|
129
132
|
end
|
130
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
|
+
|
131
140
|
def format_request(r)
|
132
141
|
data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
133
142
|
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
@@ -6,12 +6,6 @@ describe "WebSocket server" do
|
|
6
6
|
include EM::SpecHelper
|
7
7
|
default_timeout 1
|
8
8
|
|
9
|
-
def start_server
|
10
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
11
|
-
yield ws if block_given?
|
12
|
-
}
|
13
|
-
end
|
14
|
-
|
15
9
|
it "should fail on non WebSocket requests" do
|
16
10
|
em {
|
17
11
|
EM.add_timer(0.1) do
|
@@ -27,25 +21,22 @@ describe "WebSocket server" do
|
|
27
21
|
it "should expose the WebSocket request headers, path and query params" do
|
28
22
|
em {
|
29
23
|
EM.add_timer(0.1) do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}
|
36
|
-
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| }
|
37
29
|
end
|
38
30
|
|
39
31
|
start_server do |ws|
|
40
32
|
ws.onopen { |handshake|
|
41
33
|
headers = handshake.headers
|
42
|
-
headers["User-Agent"].should == "EventMachine HttpClient"
|
43
34
|
headers["Connection"].should == "Upgrade"
|
44
|
-
headers["Upgrade"].should == "
|
35
|
+
headers["Upgrade"].should == "websocket"
|
45
36
|
headers["Host"].to_s.should == "127.0.0.1:12345"
|
46
37
|
handshake.path.should == "/"
|
47
38
|
handshake.query.should == {}
|
48
|
-
handshake.origin.should ==
|
39
|
+
handshake.origin.should == 'http://example.com'
|
49
40
|
}
|
50
41
|
ws.onclose {
|
51
42
|
ws.state.should == :closed
|
@@ -58,16 +49,12 @@ describe "WebSocket server" do
|
|
58
49
|
it "should expose the WebSocket path and query params when nonempty" do
|
59
50
|
em {
|
60
51
|
EM.add_timer(0.1) do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
http.errback { fail }
|
66
|
-
http.callback {
|
67
|
-
http.response_header.status.should == 101
|
68
|
-
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
|
69
56
|
}
|
70
|
-
|
57
|
+
ws.stream { |msg| }
|
71
58
|
end
|
72
59
|
|
73
60
|
start_server do |ws|
|
@@ -104,18 +91,15 @@ describe "WebSocket server" do
|
|
104
91
|
it "should allow the server to be started inside an existing EM" do
|
105
92
|
em {
|
106
93
|
EM.add_timer(0.1) do
|
107
|
-
http = EM::HttpRequest.new('
|
108
|
-
http.errback {
|
109
|
-
http.callback {
|
110
|
-
http.response_header.status.should == 101
|
111
|
-
http.close_connection
|
112
|
-
}
|
113
|
-
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 }
|
114
97
|
end
|
115
98
|
|
116
99
|
start_server do |ws|
|
117
100
|
ws.onopen { |handshake|
|
118
|
-
handshake.headers
|
101
|
+
headers = handshake.headers
|
102
|
+
headers["Host"].to_s.should == "127.0.0.1:12345"
|
119
103
|
}
|
120
104
|
ws.onclose {
|
121
105
|
ws.state.should == :closed
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'integration/shared_examples'
|
3
2
|
|
4
3
|
describe "draft03" do
|
5
4
|
include EM::SpecHelper
|
@@ -35,12 +34,6 @@ describe "draft03" do
|
|
35
34
|
}
|
36
35
|
end
|
37
36
|
|
38
|
-
def start_server
|
39
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
40
|
-
yield ws if block_given?
|
41
|
-
}
|
42
|
-
end
|
43
|
-
|
44
37
|
def start_client
|
45
38
|
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
46
39
|
client.send_data(format_request(@request))
|
@@ -52,6 +45,10 @@ describe "draft03" do
|
|
52
45
|
let(:version) { 3 }
|
53
46
|
end
|
54
47
|
|
48
|
+
it_behaves_like "a WebSocket server drafts 3 and above" do
|
49
|
+
let(:version) { 3 }
|
50
|
+
end
|
51
|
+
|
55
52
|
# These examples are straight from the spec
|
56
53
|
# http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
|
57
54
|
describe "examples from the spec" do
|
@@ -114,7 +111,8 @@ describe "draft03" do
|
|
114
111
|
data = "a" * 256
|
115
112
|
|
116
113
|
start_server { |ws|
|
117
|
-
ws.
|
114
|
+
ws.onbinary { |msg|
|
115
|
+
msg.encoding.should == Encoding.find("BINARY") if defined?(Encoding)
|
118
116
|
msg.should == data
|
119
117
|
done
|
120
118
|
}
|
@@ -134,7 +132,8 @@ describe "draft03" do
|
|
134
132
|
data = "a" * 65536
|
135
133
|
|
136
134
|
start_server { |ws|
|
137
|
-
ws.
|
135
|
+
ws.onbinary { |msg|
|
136
|
+
msg.encoding.should == Encoding.find("BINARY") if defined?(Encoding)
|
138
137
|
msg.should == data
|
139
138
|
done
|
140
139
|
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'integration/shared_examples'
|
3
2
|
|
4
3
|
describe "draft05" do
|
5
4
|
include EM::SpecHelper
|
@@ -21,12 +20,6 @@ describe "draft05" do
|
|
21
20
|
}
|
22
21
|
}
|
23
22
|
end
|
24
|
-
|
25
|
-
def start_server
|
26
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
27
|
-
yield ws if block_given?
|
28
|
-
}
|
29
|
-
end
|
30
23
|
|
31
24
|
def start_client
|
32
25
|
client = EM.connect('0.0.0.0', 12345, Draft05FakeWebSocketClient)
|
@@ -39,6 +32,10 @@ describe "draft05" do
|
|
39
32
|
let(:version) { 5 }
|
40
33
|
end
|
41
34
|
|
35
|
+
it_behaves_like "a WebSocket server drafts 3 and above" do
|
36
|
+
let(:version) { 5 }
|
37
|
+
end
|
38
|
+
|
42
39
|
it "should report that close codes are not supported" do
|
43
40
|
em {
|
44
41
|
start_server { |ws|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'integration/shared_examples'
|
3
2
|
|
4
3
|
describe "draft06" do
|
5
4
|
include EM::SpecHelper
|
@@ -30,12 +29,6 @@ describe "draft06" do
|
|
30
29
|
}
|
31
30
|
}
|
32
31
|
end
|
33
|
-
|
34
|
-
def start_server
|
35
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
36
|
-
yield ws if block_given?
|
37
|
-
}
|
38
|
-
end
|
39
32
|
|
40
33
|
def start_client
|
41
34
|
client = EM.connect('0.0.0.0', 12345, Draft05FakeWebSocketClient)
|
@@ -48,6 +41,10 @@ describe "draft06" do
|
|
48
41
|
let(:version) { 6 }
|
49
42
|
end
|
50
43
|
|
44
|
+
it_behaves_like "a WebSocket server drafts 3 and above" do
|
45
|
+
let(:version) { 6 }
|
46
|
+
end
|
47
|
+
|
51
48
|
it "should open connection" do
|
52
49
|
em {
|
53
50
|
start_server { |server|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: BINARY
|
2
2
|
|
3
3
|
require 'helper'
|
4
|
-
require 'integration/shared_examples'
|
5
4
|
|
6
5
|
describe "draft13" do
|
7
6
|
include EM::SpecHelper
|
@@ -33,12 +32,6 @@ describe "draft13" do
|
|
33
32
|
}
|
34
33
|
end
|
35
34
|
|
36
|
-
def start_server
|
37
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
38
|
-
yield ws if block_given?
|
39
|
-
}
|
40
|
-
end
|
41
|
-
|
42
35
|
def start_client
|
43
36
|
client = EM.connect('0.0.0.0', 12345, Draft07FakeWebSocketClient)
|
44
37
|
client.send_data(format_request(@request))
|
@@ -50,6 +43,10 @@ describe "draft13" do
|
|
50
43
|
let(:version) { 13 }
|
51
44
|
end
|
52
45
|
|
46
|
+
it_behaves_like "a WebSocket server drafts 3 and above" do
|
47
|
+
let(:version) { 13 }
|
48
|
+
end
|
49
|
+
|
53
50
|
it "should send back the correct handshake response" do
|
54
51
|
em {
|
55
52
|
start_server
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'integration/shared_examples'
|
3
2
|
|
4
3
|
# These integration tests are older and use a different testing style to the
|
5
4
|
# integration tests for newer drafts. They use EM::HttpRequest which happens
|
@@ -9,12 +8,6 @@ describe "WebSocket server draft75" do
|
|
9
8
|
include EM::SpecHelper
|
10
9
|
default_timeout 1
|
11
10
|
|
12
|
-
def start_server
|
13
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
14
|
-
yield ws if block_given?
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
11
|
def start_client
|
19
12
|
client = Draft75WebSocketClient.new
|
20
13
|
yield client if block_given?
|
@@ -29,12 +22,12 @@ describe "WebSocket server draft75" do
|
|
29
22
|
em {
|
30
23
|
MSG = "Hello World!"
|
31
24
|
EventMachine.add_timer(0.1) do
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/')
|
26
|
+
ws.errback { fail }
|
27
|
+
ws.callback { }
|
35
28
|
|
36
|
-
|
37
|
-
msg.should == MSG
|
29
|
+
ws.stream { |msg|
|
30
|
+
msg.data.should == MSG
|
38
31
|
EventMachine.stop
|
39
32
|
}
|
40
33
|
end
|
@@ -53,13 +46,12 @@ describe "WebSocket server draft75" do
|
|
53
46
|
received = []
|
54
47
|
|
55
48
|
EventMachine.add_timer(0.1) do
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
http.send messages[1]
|
49
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/')
|
50
|
+
ws.errback { fail }
|
51
|
+
ws.stream {|msg|}
|
52
|
+
ws.callback {
|
53
|
+
ws.send_msg messages[0]
|
54
|
+
ws.send_msg messages[1]
|
63
55
|
}
|
64
56
|
end
|
65
57
|
|
@@ -79,13 +71,12 @@ describe "WebSocket server draft75" do
|
|
79
71
|
it "should call onclose callback when client closes connection" do
|
80
72
|
em {
|
81
73
|
EventMachine.add_timer(0.1) do
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
http.close_connection
|
74
|
+
ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/')
|
75
|
+
ws.errback { fail }
|
76
|
+
ws.callback {
|
77
|
+
ws.close_connection
|
87
78
|
}
|
88
|
-
|
79
|
+
ws.stream{|msg|}
|
89
80
|
end
|
90
81
|
|
91
82
|
start_server { |ws|
|
@@ -101,8 +92,8 @@ describe "WebSocket server draft75" do
|
|
101
92
|
it "should call onerror callback with raised exception and close connection on bad handshake" do
|
102
93
|
em {
|
103
94
|
EventMachine.add_timer(0.1) do
|
104
|
-
http =
|
105
|
-
http.errback {
|
95
|
+
http = EM::HttpRequest.new('http://127.0.0.1:12345/').get
|
96
|
+
http.errback { }
|
106
97
|
http.callback { fail }
|
107
98
|
end
|
108
99
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: BINARY
|
2
2
|
|
3
3
|
require 'helper'
|
4
|
-
require 'integration/shared_examples'
|
5
4
|
|
6
5
|
describe "WebSocket server draft76" do
|
7
6
|
include EM::SpecHelper
|
@@ -35,12 +34,6 @@ describe "WebSocket server draft76" do
|
|
35
34
|
:body => "8jKS\'y:G*Co,Wxa-"
|
36
35
|
}
|
37
36
|
end
|
38
|
-
|
39
|
-
def start_server
|
40
|
-
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
41
|
-
yield ws if block_given?
|
42
|
-
}
|
43
|
-
end
|
44
37
|
|
45
38
|
def start_client
|
46
39
|
client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
@@ -178,7 +171,7 @@ describe "WebSocket server draft76" do
|
|
178
171
|
start_server { |server|
|
179
172
|
server.onerror { |error|
|
180
173
|
error.should be_an_instance_of EM::WebSocket::HandshakeError
|
181
|
-
error.message.should == "Invalid HTTP header: Could not parse data entirely"
|
174
|
+
error.message.should == "Invalid HTTP header: Could not parse data entirely (1 != 29)"
|
182
175
|
done
|
183
176
|
}
|
184
177
|
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
shared_examples_for "a WebSocket server drafts 3 and above" do
|
2
|
+
it "should force close connections after a timeout if close handshake is not sent by the client" do
|
3
|
+
em {
|
4
|
+
server_onerror_fired = false
|
5
|
+
server_onclose_fired = false
|
6
|
+
client_got_close_handshake = false
|
7
|
+
|
8
|
+
start_server(:close_timeout => 0.1) { |ws|
|
9
|
+
ws.onopen {
|
10
|
+
# 1: Send close handshake to client
|
11
|
+
EM.next_tick { ws.close(4999, "Close message") }
|
12
|
+
}
|
13
|
+
|
14
|
+
ws.onerror { |e|
|
15
|
+
# 3: Client should receive onerror
|
16
|
+
e.class.should == EM::WebSocket::WSProtocolError
|
17
|
+
e.message.should == "Close handshake un-acked after 0.1s, closing tcp connection"
|
18
|
+
server_onerror_fired = true
|
19
|
+
}
|
20
|
+
|
21
|
+
ws.onclose {
|
22
|
+
server_onclose_fired = true
|
23
|
+
}
|
24
|
+
}
|
25
|
+
start_client { |client|
|
26
|
+
client.onmessage { |msg|
|
27
|
+
# 2: Client does not respond to close handshake (the fake client
|
28
|
+
# doesn't understand them at all hence this is in onmessage)
|
29
|
+
msg.should =~ /Close message/ if version >= 6
|
30
|
+
client_got_close_handshake = true
|
31
|
+
}
|
32
|
+
|
33
|
+
client.onclose {
|
34
|
+
server_onerror_fired.should == true
|
35
|
+
server_onclose_fired.should == true
|
36
|
+
client_got_close_handshake.should == true
|
37
|
+
done
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
data/spec/unit/framing_spec.rb
CHANGED
@@ -15,7 +15,7 @@ describe EM::WebSocket::Framing03 do
|
|
15
15
|
|
16
16
|
def <<(data)
|
17
17
|
@data << data
|
18
|
-
process_data
|
18
|
+
process_data
|
19
19
|
end
|
20
20
|
|
21
21
|
def debug(*args); end
|
@@ -140,7 +140,7 @@ describe EM::WebSocket::Framing04 do
|
|
140
140
|
|
141
141
|
def <<(data)
|
142
142
|
@data << data
|
143
|
-
process_data
|
143
|
+
process_data
|
144
144
|
end
|
145
145
|
|
146
146
|
def debug(*args); end
|
@@ -209,7 +209,7 @@ describe EM::WebSocket::Framing07 do
|
|
209
209
|
|
210
210
|
def <<(data)
|
211
211
|
@data << data
|
212
|
-
process_data
|
212
|
+
process_data
|
213
213
|
end
|
214
214
|
|
215
215
|
def debug(*args); end
|
@@ -276,5 +276,23 @@ describe EM::WebSocket::Framing07 do
|
|
276
276
|
@f << "\x00\x02lo"
|
277
277
|
@f << "\x80\x06 world"
|
278
278
|
end
|
279
|
+
|
280
|
+
it "should raise if non-fin frame is followed by a non-continuation data frame (continuation frame would be expected)" do
|
281
|
+
lambda {
|
282
|
+
@f << 0b00000001 # Not fin, text
|
283
|
+
@f << 0b00000001 # Length 1
|
284
|
+
@f << 'f'
|
285
|
+
@f << 0b10000001 # fin, text (continutation expected)
|
286
|
+
@f << 0b00000001 # Length 1
|
287
|
+
@f << 'b'
|
288
|
+
}.should raise_error(EM::WebSocket::WebSocketError, 'Continuation frame expected')
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should raise on non-fin control frames (control frames must not be fragmented)" do
|
292
|
+
lambda {
|
293
|
+
@f << 0b00001010 # Not fin, pong (opcode 10)
|
294
|
+
@f << 0b00000000 # Length 1
|
295
|
+
}.should raise_error(EM::WebSocket::WebSocketError, 'Control frames must not be fragmented')
|
296
|
+
end
|
279
297
|
end
|
280
298
|
end
|
data/spec/unit/handshake_spec.rb
CHANGED
@@ -126,6 +126,23 @@ describe EM::WebSocket::Handshake do
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
+
it "should raise error if Sec-WebSocket-Key1 is missing" do
|
130
|
+
@request[:headers].delete("Sec-WebSocket-Key1")
|
131
|
+
|
132
|
+
# The error message isn't correct since key1 is used to heuristically
|
133
|
+
# determine the protocol version in use, however this test at least checks
|
134
|
+
# that the handshake does correctly fail
|
135
|
+
handshake(@request).
|
136
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Extra bytes after header')
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should raise error if Sec-WebSocket-Key2 is missing" do
|
140
|
+
@request[:headers].delete("Sec-WebSocket-Key2")
|
141
|
+
|
142
|
+
handshake(@request).
|
143
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'WebSocket key1 or key2 is missing')
|
144
|
+
end
|
145
|
+
|
129
146
|
it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
|
130
147
|
@request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
|
131
148
|
|
@@ -138,7 +155,7 @@ describe EM::WebSocket::Handshake do
|
|
138
155
|
handshake.receive_data("\r\n\r\nfoobar")
|
139
156
|
|
140
157
|
handshake.
|
141
|
-
should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid HTTP header: Could not parse data entirely')
|
158
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid HTTP header: Could not parse data entirely (4 != 10)')
|
142
159
|
end
|
143
160
|
|
144
161
|
# This might seems crazy, but very occasionally we saw multiple "Upgrade:
|
@@ -190,4 +207,10 @@ describe EM::WebSocket::Handshake do
|
|
190
207
|
|
191
208
|
handshake(@request).should succeed_with_upgrade(@response)
|
192
209
|
end
|
210
|
+
|
211
|
+
it "should fail if the request URI is invalid" do
|
212
|
+
@request[:path] = "/%"
|
213
|
+
handshake(@request).should \
|
214
|
+
fail_with_error(EM::WebSocket::HandshakeError, 'Invalid request URI: /%')
|
215
|
+
end
|
193
216
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-websocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
5
|
-
prerelease:
|
4
|
+
version: 0.5.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Ilya Grigorik
|
@@ -10,120 +9,36 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2014-04-23 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: eventmachine
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - ">="
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: 0.12.9
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - ">="
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: 0.12.9
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: http_parser.rb
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- - ~>
|
32
|
+
- - "~>"
|
37
33
|
- !ruby/object:Gem::Version
|
38
|
-
version: 0.
|
34
|
+
version: 0.6.0
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- - ~>
|
39
|
+
- - "~>"
|
45
40
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: em-spec
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.2.6
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
|
-
requirements:
|
60
|
-
- - ~>
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 0.2.6
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: eventmachine
|
65
|
-
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
|
-
requirements:
|
68
|
-
- - ! '>='
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: '0'
|
71
|
-
type: :development
|
72
|
-
prerelease: false
|
73
|
-
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
|
-
requirements:
|
76
|
-
- - ! '>='
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: '0'
|
79
|
-
- !ruby/object:Gem::Dependency
|
80
|
-
name: em-http-request
|
81
|
-
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
|
-
requirements:
|
84
|
-
- - ~>
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version: 0.2.6
|
87
|
-
type: :development
|
88
|
-
prerelease: false
|
89
|
-
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
|
-
requirements:
|
92
|
-
- - ~>
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 0.2.6
|
95
|
-
- !ruby/object:Gem::Dependency
|
96
|
-
name: rspec
|
97
|
-
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
|
-
requirements:
|
100
|
-
- - ~>
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: 2.12.0
|
103
|
-
type: :development
|
104
|
-
prerelease: false
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
|
-
requirements:
|
108
|
-
- - ~>
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: 2.12.0
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rake
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
|
-
requirements:
|
116
|
-
- - ! '>='
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
version: '0'
|
119
|
-
type: :development
|
120
|
-
prerelease: false
|
121
|
-
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
|
-
requirements:
|
124
|
-
- - ! '>='
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
version: '0'
|
41
|
+
version: 0.6.0
|
127
42
|
description: EventMachine based WebSocket server
|
128
43
|
email:
|
129
44
|
- ilya@igvita.com
|
@@ -132,7 +47,7 @@ executables: []
|
|
132
47
|
extensions: []
|
133
48
|
extra_rdoc_files: []
|
134
49
|
files:
|
135
|
-
- .gitignore
|
50
|
+
- ".gitignore"
|
136
51
|
- CHANGELOG.rdoc
|
137
52
|
- Gemfile
|
138
53
|
- README.md
|
@@ -180,39 +95,33 @@ files:
|
|
180
95
|
- spec/integration/draft13_spec.rb
|
181
96
|
- spec/integration/draft75_spec.rb
|
182
97
|
- spec/integration/draft76_spec.rb
|
98
|
+
- spec/integration/gte_03_examples.rb
|
183
99
|
- spec/integration/shared_examples.rb
|
184
100
|
- spec/unit/framing_spec.rb
|
185
101
|
- spec/unit/handshake_spec.rb
|
186
102
|
- spec/unit/masking_spec.rb
|
187
103
|
homepage: http://github.com/igrigorik/em-websocket
|
188
104
|
licenses: []
|
105
|
+
metadata: {}
|
189
106
|
post_install_message:
|
190
107
|
rdoc_options: []
|
191
108
|
require_paths:
|
192
109
|
- lib
|
193
110
|
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
-
none: false
|
195
111
|
requirements:
|
196
|
-
- -
|
112
|
+
- - ">="
|
197
113
|
- !ruby/object:Gem::Version
|
198
114
|
version: '0'
|
199
|
-
segments:
|
200
|
-
- 0
|
201
|
-
hash: 3271699693430314189
|
202
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
|
-
none: false
|
204
116
|
requirements:
|
205
|
-
- -
|
117
|
+
- - ">="
|
206
118
|
- !ruby/object:Gem::Version
|
207
119
|
version: '0'
|
208
|
-
segments:
|
209
|
-
- 0
|
210
|
-
hash: 3271699693430314189
|
211
120
|
requirements: []
|
212
121
|
rubyforge_project: em-websocket
|
213
|
-
rubygems_version:
|
122
|
+
rubygems_version: 2.2.2
|
214
123
|
signing_key:
|
215
|
-
specification_version:
|
124
|
+
specification_version: 4
|
216
125
|
summary: EventMachine based WebSocket server
|
217
126
|
test_files:
|
218
127
|
- spec/helper.rb
|
@@ -223,6 +132,7 @@ test_files:
|
|
223
132
|
- spec/integration/draft13_spec.rb
|
224
133
|
- spec/integration/draft75_spec.rb
|
225
134
|
- spec/integration/draft76_spec.rb
|
135
|
+
- spec/integration/gte_03_examples.rb
|
226
136
|
- spec/integration/shared_examples.rb
|
227
137
|
- spec/unit/framing_spec.rb
|
228
138
|
- spec/unit/handshake_spec.rb
|