websocket-rack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +105 -0
- data/Rakefile +11 -0
- data/example/example.ru +31 -0
- data/example/html/FABridge.js +604 -0
- data/example/html/WebSocketMain.swf +0 -0
- data/example/html/index.html +76 -0
- data/example/html/swfobject.js +4 -0
- data/example/html/web_socket.js +388 -0
- data/lib/rack/websocket/application.rb +65 -0
- data/lib/rack/websocket/connection.rb +105 -0
- data/lib/rack/websocket/debugger.rb +17 -0
- data/lib/rack/websocket/extensions/thin/connection.rb +71 -0
- data/lib/rack/websocket/extensions/thin.rb +16 -0
- data/lib/rack/websocket/framing03.rb +178 -0
- data/lib/rack/websocket/framing76.rb +115 -0
- data/lib/rack/websocket/handler.rb +43 -0
- data/lib/rack/websocket/handler03.rb +14 -0
- data/lib/rack/websocket/handler75.rb +8 -0
- data/lib/rack/websocket/handler76.rb +11 -0
- data/lib/rack/websocket/handler_factory.rb +61 -0
- data/lib/rack/websocket/handshake75.rb +21 -0
- data/lib/rack/websocket/handshake76.rb +71 -0
- data/lib/rack/websocket.rb +40 -0
- data/spec/helper.rb +44 -0
- data/spec/integration/draft03_spec.rb +252 -0
- data/spec/integration/draft76_spec.rb +212 -0
- data/spec/unit/framing_spec.rb +108 -0
- data/spec/unit/handler_spec.rb +136 -0
- data/spec/websocket_spec.rb +210 -0
- data/websocket-rack.gemspec +23 -0
- metadata +147 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module WebSocket
|
5
|
+
module Framing76
|
6
|
+
|
7
|
+
# Set the max frame lenth to very high value (10MB) until there is a
|
8
|
+
# limit specified in the spec to protect against malicious attacks
|
9
|
+
MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
|
10
|
+
|
11
|
+
def initialize_framing
|
12
|
+
@data = ''
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_data(newdata)
|
16
|
+
debug [:message, @data]
|
17
|
+
|
18
|
+
# This algorithm comes straight from the spec
|
19
|
+
# http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-5.3
|
20
|
+
|
21
|
+
error = false
|
22
|
+
|
23
|
+
while !error
|
24
|
+
return if @data.size == 0
|
25
|
+
|
26
|
+
pointer = 0
|
27
|
+
frame_type = @data.getbyte(pointer)
|
28
|
+
pointer += 1
|
29
|
+
|
30
|
+
if (frame_type & 0x80) == 0x80
|
31
|
+
# If the high-order bit of the /frame type/ byte is set
|
32
|
+
length = 0
|
33
|
+
|
34
|
+
loop do
|
35
|
+
return false if !@data.getbyte(pointer)
|
36
|
+
b = @data.getbyte(pointer)
|
37
|
+
pointer += 1
|
38
|
+
b_v = b & 0x7F
|
39
|
+
length = length * 128 + b_v
|
40
|
+
break unless (b & 0x80) == 0x80
|
41
|
+
end
|
42
|
+
|
43
|
+
# Addition to the spec to protect against malicious requests
|
44
|
+
if length > MAXIMUM_FRAME_LENGTH
|
45
|
+
@connection.close_with_error(DataError.new("Frame length too long (#{length} bytes)"))
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
if @data.getbyte(pointer+length-1) == nil
|
50
|
+
debug [:buffer_incomplete, @data.inspect]
|
51
|
+
# Incomplete data - leave @data to accumulate
|
52
|
+
error = true
|
53
|
+
else
|
54
|
+
# Straight from spec - I'm sure this isn't crazy...
|
55
|
+
# 6. Read /length/ bytes.
|
56
|
+
# 7. Discard the read bytes.
|
57
|
+
@data = @data[(pointer+length)..-1]
|
58
|
+
|
59
|
+
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
60
|
+
if length == 0
|
61
|
+
@connection.send_data("\xff\x00")
|
62
|
+
@state = :closing
|
63
|
+
@connection.close_connection_after_writing
|
64
|
+
else
|
65
|
+
error = true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
# If the high-order bit of the /frame type/ byte is _not_ set
|
70
|
+
|
71
|
+
if @data.getbyte(0) != 0x00
|
72
|
+
# Close the connection since this buffer can never match
|
73
|
+
@connection.close_with_error(DataError.new("Invalid frame received"))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Addition to the spec to protect against malicious requests
|
77
|
+
if @data.size > MAXIMUM_FRAME_LENGTH
|
78
|
+
@connection.close_with_error(DataError.new("Frame length too long (#{@data.size} bytes)"))
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Optimization to avoid calling slice! unnecessarily
|
83
|
+
error = true and next unless newdata =~ /\xff/
|
84
|
+
|
85
|
+
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
86
|
+
if msg
|
87
|
+
msg.gsub!(/\A\x00|\xff\z/, '')
|
88
|
+
if @state == :closing
|
89
|
+
debug [:ignored_message, msg]
|
90
|
+
else
|
91
|
+
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
92
|
+
@connection.trigger_on_message(msg)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
error = true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
# frames need to start with 0x00-0x7f byte and end with
|
104
|
+
# an 0xFF byte. Per spec, we can also set the first
|
105
|
+
# byte to a value betweent 0x80 and 0xFF, followed by
|
106
|
+
# a leading length indicator
|
107
|
+
def send_text_frame(data)
|
108
|
+
ary = ["\x00", data, "\xff"]
|
109
|
+
ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
|
110
|
+
@connection.send_data(ary.join)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
class Handler
|
4
|
+
include Debugger
|
5
|
+
|
6
|
+
attr_reader :request, :state
|
7
|
+
|
8
|
+
def initialize(connection, request, debug = false)
|
9
|
+
@connection, @request = connection, request
|
10
|
+
@debug = debug
|
11
|
+
@state = :handshake
|
12
|
+
initialize_framing
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
@connection.send_data handshake
|
17
|
+
@state = :connected
|
18
|
+
@connection.trigger_on_open
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handshake response
|
22
|
+
def handshake
|
23
|
+
# Implemented in subclass
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_data(data)
|
27
|
+
@data << data
|
28
|
+
process_data(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def close_websocket
|
32
|
+
# Unless redefined in a subclass, just close the connection
|
33
|
+
@state = :closed
|
34
|
+
@connection.close_connection_after_writing
|
35
|
+
end
|
36
|
+
|
37
|
+
def unbind
|
38
|
+
@state = :closed
|
39
|
+
@connection.trigger_on_close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
class Handler03 < Handler
|
4
|
+
include Handshake76
|
5
|
+
include Framing03
|
6
|
+
|
7
|
+
def close_websocket
|
8
|
+
# TODO: Should we send data and check the response matches?
|
9
|
+
send_frame(:close, '')
|
10
|
+
@state = :closing
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
class HandlerFactory
|
4
|
+
|
5
|
+
def self.build(connection, data, debug = false)
|
6
|
+
request = Rack::Request.new(data)
|
7
|
+
|
8
|
+
unless request.env['rack.input'].nil?
|
9
|
+
request.env['rack.input'].rewind
|
10
|
+
remains = request.env['rack.input'].read
|
11
|
+
else
|
12
|
+
# The whole header has not been received yet.
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
unless request.get?
|
17
|
+
raise HandshakeError, "Must be GET request"
|
18
|
+
end
|
19
|
+
|
20
|
+
version = request.env['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
|
21
|
+
case version
|
22
|
+
when 75
|
23
|
+
if !remains.empty?
|
24
|
+
raise HandshakeError, "Extra bytes after header"
|
25
|
+
end
|
26
|
+
when 76
|
27
|
+
if remains.length < 8
|
28
|
+
# The whole third-key has not been received yet.
|
29
|
+
return nil
|
30
|
+
elsif remains.length > 8
|
31
|
+
raise HandshakeError, "Extra bytes after third key"
|
32
|
+
end
|
33
|
+
request.env['HTTP_THIRD_KEY'] = remains
|
34
|
+
else
|
35
|
+
raise WebSocketError, "Must not happen"
|
36
|
+
end
|
37
|
+
|
38
|
+
unless request.env['HTTP_CONNECTION'] == 'Upgrade' and request.env['HTTP_UPGRADE'] == 'WebSocket'
|
39
|
+
raise HandshakeError, "Connection and Upgrade headers required"
|
40
|
+
end
|
41
|
+
|
42
|
+
# transform headers
|
43
|
+
request.env['rack.url_scheme'] = (request.scheme == 'https' ? "wss" : "ws")
|
44
|
+
|
45
|
+
if version = request.env['HTTP_SEC_WEBSOCKET_DRAFT']
|
46
|
+
if version == '1' || version == '2' || version == '3'
|
47
|
+
# We'll use handler03 - I believe they're all compatible
|
48
|
+
Handler03.new(connection, request, debug)
|
49
|
+
else
|
50
|
+
# According to spec should abort the connection
|
51
|
+
raise WebSocketError, "Unknown draft version: #{version}"
|
52
|
+
end
|
53
|
+
elsif request.env['HTTP_SEC_WEBSOCKET_KEY1']
|
54
|
+
Handler76.new(connection, request, debug)
|
55
|
+
else
|
56
|
+
Handler75.new(connection, request, debug)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
module Handshake75
|
4
|
+
def handshake
|
5
|
+
location = "#{request.env['rack.url_scheme']}://#{request.host}"
|
6
|
+
location << ":#{request.port}" if request.port > 0
|
7
|
+
location << request.path
|
8
|
+
|
9
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
10
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
11
|
+
upgrade << "Connection: Upgrade\r\n"
|
12
|
+
upgrade << "WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
13
|
+
upgrade << "WebSocket-Location: #{location}\r\n\r\n"
|
14
|
+
|
15
|
+
debug [:upgrade_headers, upgrade]
|
16
|
+
|
17
|
+
return upgrade
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module WebSocket
|
5
|
+
module Handshake76
|
6
|
+
def handshake
|
7
|
+
challenge_response = solve_challenge(
|
8
|
+
request.env['HTTP_SEC_WEBSOCKET_KEY1'],
|
9
|
+
request.env['HTTP_SEC_WEBSOCKET_KEY2'],
|
10
|
+
request.env['HTTP_THIRD_KEY']
|
11
|
+
)
|
12
|
+
|
13
|
+
location = "#{request.env['rack.url_scheme']}://#{request.host}"
|
14
|
+
location << ":#{request.port}" if request.port > 0
|
15
|
+
location << request.path
|
16
|
+
|
17
|
+
upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
18
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
19
|
+
upgrade << "Connection: Upgrade\r\n"
|
20
|
+
upgrade << "Sec-WebSocket-Location: #{location}\r\n"
|
21
|
+
upgrade << "Sec-WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
22
|
+
if protocol = request.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
23
|
+
validate_protocol!(protocol)
|
24
|
+
upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
|
25
|
+
end
|
26
|
+
upgrade << "\r\n"
|
27
|
+
upgrade << challenge_response
|
28
|
+
|
29
|
+
debug [:upgrade_headers, upgrade]
|
30
|
+
|
31
|
+
return upgrade
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def solve_challenge(first, second, third)
|
37
|
+
# Refer to 5.2 4-9 of the draft 76
|
38
|
+
sum = [numbers_over_spaces(first)].pack("N*") +
|
39
|
+
[numbers_over_spaces(second)].pack("N*") +
|
40
|
+
third
|
41
|
+
Digest::MD5.digest(sum)
|
42
|
+
end
|
43
|
+
|
44
|
+
def numbers_over_spaces(string)
|
45
|
+
numbers = string.scan(/[0-9]/).join.to_i
|
46
|
+
|
47
|
+
spaces = string.scan(/ /).size
|
48
|
+
# As per 5.2.5, abort the connection if spaces are zero.
|
49
|
+
raise HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0
|
50
|
+
|
51
|
+
# As per 5.2.6, abort if numbers is not an integral multiple of spaces
|
52
|
+
if numbers % spaces != 0
|
53
|
+
raise HandshakeError, "Invalid Key #{string.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
quotient = numbers / spaces
|
57
|
+
|
58
|
+
if quotient > 2**32-1
|
59
|
+
raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
return quotient
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_protocol!(protocol)
|
66
|
+
raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
|
67
|
+
# TODO: Validate characters
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module WebSocket
|
5
|
+
VERSION = "0.1.0"
|
6
|
+
ROOT_PATH = ::File.expand_path(::File.dirname(__FILE__))
|
7
|
+
|
8
|
+
class WebSocketError < RuntimeError; end
|
9
|
+
class HandshakeError < WebSocketError; end
|
10
|
+
class DataError < WebSocketError; end
|
11
|
+
|
12
|
+
autoload :Application, "#{ROOT_PATH}/websocket/application"
|
13
|
+
autoload :Connection, "#{ROOT_PATH}/websocket/connection"
|
14
|
+
autoload :Debugger, "#{ROOT_PATH}/websocket/debugger"
|
15
|
+
autoload :Framing03, "#{ROOT_PATH}/websocket/framing03"
|
16
|
+
autoload :Framing76, "#{ROOT_PATH}/websocket/framing76"
|
17
|
+
autoload :Handler, "#{ROOT_PATH}/websocket/handler"
|
18
|
+
autoload :Handler03, "#{ROOT_PATH}/websocket/handler03"
|
19
|
+
autoload :Handler75, "#{ROOT_PATH}/websocket/handler75"
|
20
|
+
autoload :Handler76, "#{ROOT_PATH}/websocket/handler76"
|
21
|
+
autoload :HandlerFactory, "#{ROOT_PATH}/websocket/handler_factory"
|
22
|
+
autoload :Handshake75, "#{ROOT_PATH}/websocket/handshake75"
|
23
|
+
autoload :Handshake76, "#{ROOT_PATH}/websocket/handshake76"
|
24
|
+
|
25
|
+
module Extensions
|
26
|
+
autoload :Thin, "#{ROOT_PATH}/websocket/extensions/thin"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
::Thin.send(:include, ::Rack::WebSocket::Extensions::Thin) if defined?(Thin)
|
33
|
+
|
34
|
+
unless ''.respond_to?(:getbyte)
|
35
|
+
class String
|
36
|
+
def getbyte(i)
|
37
|
+
self[i]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'pp'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
require 'rack/websocket'
|
7
|
+
|
8
|
+
Rspec.configure do |c|
|
9
|
+
c.mock_with :rspec
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_request(r)
|
13
|
+
data = {}
|
14
|
+
data['REQUEST_METHOD'] = r[:method] if r[:method]
|
15
|
+
data['PATH_INFO'] = r[:path] if r[:path]
|
16
|
+
data['SERVER_PORT'] = r[:port] if r[:port] && r[:port] != 80
|
17
|
+
r[:headers].each do |key, value|
|
18
|
+
data['HTTP_' + key.upcase.gsub('-','_')] = value
|
19
|
+
end
|
20
|
+
data['rack.input'] = StringIO.new(r[:body]) if r[:body]
|
21
|
+
# data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
22
|
+
# header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
23
|
+
# data << [header_lines, '', r[:body]].join("\r\n")
|
24
|
+
data
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_response(r)
|
28
|
+
data = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
29
|
+
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
30
|
+
data << [header_lines, '', r[:body]].join("\r\n")
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
def handler(request, secure = false)
|
35
|
+
connection = Object.new
|
36
|
+
secure_hash = secure ? {'rack.url_scheme' => 'https'} : {}
|
37
|
+
Rack::WebSocket::HandlerFactory.build(connection, format_request(request).merge(secure_hash))
|
38
|
+
end
|
39
|
+
|
40
|
+
RSpec::Matchers.define :send_handshake do |response|
|
41
|
+
match do |actual|
|
42
|
+
actual.handshake.lines.sort == format_response(response).lines.sort
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# require 'helper'
|
2
|
+
#
|
3
|
+
# describe "draft03" do
|
4
|
+
# before :each do
|
5
|
+
# @request = {
|
6
|
+
# :port => 80,
|
7
|
+
# :method => "GET",
|
8
|
+
# :path => "/demo",
|
9
|
+
# :headers => {
|
10
|
+
# 'Host' => 'example.com',
|
11
|
+
# 'Connection' => 'Upgrade',
|
12
|
+
# 'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
13
|
+
# 'Sec-WebSocket-Protocol' => 'sample',
|
14
|
+
# 'Upgrade' => 'WebSocket',
|
15
|
+
# 'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
16
|
+
# 'Origin' => 'http://example.com',
|
17
|
+
# 'Sec-WebSocket-Draft' => '3'
|
18
|
+
# },
|
19
|
+
# :body => '^n:ds[4U'
|
20
|
+
# }
|
21
|
+
#
|
22
|
+
# @response = {
|
23
|
+
# :headers => {
|
24
|
+
# "Upgrade" => "WebSocket",
|
25
|
+
# "Connection" => "Upgrade",
|
26
|
+
# "Sec-WebSocket-Location" => "ws://example.com/demo",
|
27
|
+
# "Sec-WebSocket-Origin" => "http://example.com",
|
28
|
+
# "Sec-WebSocket-Protocol" => "sample"
|
29
|
+
# },
|
30
|
+
# :body => "8jKS\'y:G*Co,Wxa-"
|
31
|
+
# }
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # These examples are straight from the spec
|
35
|
+
# # http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
|
36
|
+
# describe "examples from the spec" do
|
37
|
+
# it "should accept a single-frame text message" do
|
38
|
+
# EM.run do
|
39
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
40
|
+
# ws.onmessage { |msg|
|
41
|
+
# msg.should == 'Hello'
|
42
|
+
# EM.stop
|
43
|
+
# }
|
44
|
+
# }
|
45
|
+
#
|
46
|
+
# # Create a fake client which sends draft 76 handshake
|
47
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
48
|
+
# connection.send_data(format_request(@request))
|
49
|
+
#
|
50
|
+
# # Send frame
|
51
|
+
# connection.onopen = lambda {
|
52
|
+
# connection.send_data("\x04\x05Hello")
|
53
|
+
# }
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# it "should accept a fragmented text message" do
|
58
|
+
# EM.run do
|
59
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
60
|
+
# ws.onmessage { |msg|
|
61
|
+
# msg.should == 'Hello'
|
62
|
+
# EM.stop
|
63
|
+
# }
|
64
|
+
# }
|
65
|
+
#
|
66
|
+
# # Create a fake client which sends draft 76 handshake
|
67
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
68
|
+
# connection.send_data(format_request(@request))
|
69
|
+
#
|
70
|
+
# # Send frame
|
71
|
+
# connection.onopen = lambda {
|
72
|
+
# connection.send_data("\x84\x03Hel")
|
73
|
+
# connection.send_data("\x00\x02lo")
|
74
|
+
# }
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# it "should accept a ping request and respond with the same body" do
|
79
|
+
# EM.run do
|
80
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
|
81
|
+
#
|
82
|
+
# # Create a fake client which sends draft 76 handshake
|
83
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
84
|
+
# connection.send_data(format_request(@request))
|
85
|
+
#
|
86
|
+
# # Send frame
|
87
|
+
# connection.onopen = lambda {
|
88
|
+
# connection.send_data("\x02\x05Hello")
|
89
|
+
# }
|
90
|
+
#
|
91
|
+
# connection.onmessage = lambda { |frame|
|
92
|
+
# next if frame.nil?
|
93
|
+
# frame.should == "\x03\x05Hello"
|
94
|
+
# EM.stop
|
95
|
+
# }
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# it "should accept a 256 bytes binary message in a single frame" do
|
100
|
+
# EM.run do
|
101
|
+
# data = "a" * 256
|
102
|
+
#
|
103
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
104
|
+
# ws.onmessage { |msg|
|
105
|
+
# msg.should == data
|
106
|
+
# EM.stop
|
107
|
+
# }
|
108
|
+
# }
|
109
|
+
#
|
110
|
+
# # Create a fake client which sends draft 76 handshake
|
111
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
112
|
+
# connection.send_data(format_request(@request))
|
113
|
+
#
|
114
|
+
# # Send frame
|
115
|
+
# connection.onopen = lambda {
|
116
|
+
# connection.send_data("\x05\x7E\x01\x00" + data)
|
117
|
+
# }
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# it "should accept a 64KiB binary message in a single frame" do
|
122
|
+
# EM.run do
|
123
|
+
# data = "a" * 65536
|
124
|
+
#
|
125
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
126
|
+
# ws.onmessage { |msg|
|
127
|
+
# msg.should == data
|
128
|
+
# EM.stop
|
129
|
+
# }
|
130
|
+
# }
|
131
|
+
#
|
132
|
+
# # Create a fake client which sends draft 76 handshake
|
133
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
134
|
+
# connection.send_data(format_request(@request))
|
135
|
+
#
|
136
|
+
# # Send frame
|
137
|
+
# connection.onopen = lambda {
|
138
|
+
# connection.send_data("\x05\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data)
|
139
|
+
# }
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# describe "close handling" do
|
145
|
+
# it "should respond to a new close frame with a close frame" do
|
146
|
+
# EM.run do
|
147
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
|
148
|
+
#
|
149
|
+
# # Create a fake client which sends draft 76 handshake
|
150
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
151
|
+
# connection.send_data(format_request(@request))
|
152
|
+
#
|
153
|
+
# # Send close frame
|
154
|
+
# connection.onopen = lambda {
|
155
|
+
# connection.send_data("\x01\x00")
|
156
|
+
# }
|
157
|
+
#
|
158
|
+
# # Check that close ack received
|
159
|
+
# connection.onmessage = lambda { |frame|
|
160
|
+
# frame.should == "\x01\x00"
|
161
|
+
# EM.stop
|
162
|
+
# }
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# it "should close the connection on receiving a close acknowlegement" do
|
167
|
+
# EM.run do
|
168
|
+
# ack_received = false
|
169
|
+
#
|
170
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
171
|
+
# ws.onopen {
|
172
|
+
# # 2. Send a close frame
|
173
|
+
# EM.next_tick {
|
174
|
+
# ws.close_websocket
|
175
|
+
# }
|
176
|
+
# }
|
177
|
+
# }
|
178
|
+
#
|
179
|
+
# # 1. Create a fake client which sends draft 76 handshake
|
180
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
181
|
+
# connection.send_data(format_request(@request))
|
182
|
+
#
|
183
|
+
# # 3. Check that close frame recieved and acknowlege it
|
184
|
+
# connection.onmessage = lambda { |frame|
|
185
|
+
# frame.should == "\x01\x00"
|
186
|
+
# ack_received = true
|
187
|
+
# connection.send_data("\x01\x00")
|
188
|
+
# }
|
189
|
+
#
|
190
|
+
# # 4. Check that connection is closed _after_ the ack
|
191
|
+
# connection.onclose = lambda {
|
192
|
+
# ack_received.should == true
|
193
|
+
# EM.stop
|
194
|
+
# }
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# it "should not allow data frame to be sent after close frame sent" do
|
199
|
+
# EM.run {
|
200
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
201
|
+
# ws.onopen {
|
202
|
+
# # 2. Send a close frame
|
203
|
+
# EM.next_tick {
|
204
|
+
# ws.close_websocket
|
205
|
+
# }
|
206
|
+
#
|
207
|
+
# # 3. Check that exception raised if I attempt to send more data
|
208
|
+
# EM.add_timer(0.1) {
|
209
|
+
# lambda {
|
210
|
+
# ws.send('hello world')
|
211
|
+
# }.should raise_error(EM::WebSocket::WebSocketError, 'Cannot send data frame since connection is closing')
|
212
|
+
# EM.stop
|
213
|
+
# }
|
214
|
+
# }
|
215
|
+
# }
|
216
|
+
#
|
217
|
+
# # 1. Create a fake client which sends draft 76 handshake
|
218
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
219
|
+
# connection.send_data(format_request(@request))
|
220
|
+
# }
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# it "should still respond to control frames after close frame sent" do
|
224
|
+
# EM.run {
|
225
|
+
# EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
226
|
+
# ws.onopen {
|
227
|
+
# # 2. Send a close frame
|
228
|
+
# EM.next_tick {
|
229
|
+
# ws.close_websocket
|
230
|
+
# }
|
231
|
+
# }
|
232
|
+
# }
|
233
|
+
#
|
234
|
+
# # 1. Create a fake client which sends draft 76 handshake
|
235
|
+
# connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
236
|
+
# connection.send_data(format_request(@request))
|
237
|
+
#
|
238
|
+
# connection.onmessage = lambda { |frame|
|
239
|
+
# if frame == "\x01\x00"
|
240
|
+
# # 3. After the close frame is received send a ping frame, but
|
241
|
+
# # don't respond with a close ack
|
242
|
+
# connection.send_data("\x02\x05Hello")
|
243
|
+
# else
|
244
|
+
# # 4. Check that the pong is received
|
245
|
+
# frame.should == "\x03\x05Hello"
|
246
|
+
# EM.stop
|
247
|
+
# end
|
248
|
+
# }
|
249
|
+
# }
|
250
|
+
# end
|
251
|
+
# end
|
252
|
+
# end
|