websocket 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/README.md +117 -0
- data/Rakefile +23 -0
- data/autobahn-client.json +11 -0
- data/autobahn-server.json +10 -0
- data/examples/base.rb +162 -0
- data/examples/client.rb +70 -0
- data/examples/server.rb +56 -0
- data/examples/tests/autobahn_client.rb +52 -0
- data/examples/tests/echo_client.rb +42 -0
- data/examples/tests/echo_server.rb +45 -0
- data/lib/websocket.rb +16 -0
- data/lib/websocket/frame.rb +11 -0
- data/lib/websocket/frame/base.rb +45 -0
- data/lib/websocket/frame/data.rb +52 -0
- data/lib/websocket/frame/handler.rb +15 -0
- data/lib/websocket/frame/handler/base.rb +41 -0
- data/lib/websocket/frame/handler/handler03.rb +162 -0
- data/lib/websocket/frame/handler/handler04.rb +19 -0
- data/lib/websocket/frame/handler/handler05.rb +17 -0
- data/lib/websocket/frame/handler/handler07.rb +34 -0
- data/lib/websocket/frame/handler/handler75.rb +79 -0
- data/lib/websocket/frame/incoming.rb +43 -0
- data/lib/websocket/frame/incoming/client.rb +9 -0
- data/lib/websocket/frame/incoming/server.rb +9 -0
- data/lib/websocket/frame/outgoing.rb +28 -0
- data/lib/websocket/frame/outgoing/client.rb +9 -0
- data/lib/websocket/frame/outgoing/server.rb +9 -0
- data/lib/websocket/handshake.rb +10 -0
- data/lib/websocket/handshake/base.rb +67 -0
- data/lib/websocket/handshake/client.rb +80 -0
- data/lib/websocket/handshake/handler.rb +20 -0
- data/lib/websocket/handshake/handler/base.rb +25 -0
- data/lib/websocket/handshake/handler/client.rb +19 -0
- data/lib/websocket/handshake/handler/client01.rb +19 -0
- data/lib/websocket/handshake/handler/client04.rb +47 -0
- data/lib/websocket/handshake/handler/client75.rb +25 -0
- data/lib/websocket/handshake/handler/client76.rb +85 -0
- data/lib/websocket/handshake/handler/server.rb +30 -0
- data/lib/websocket/handshake/handler/server04.rb +38 -0
- data/lib/websocket/handshake/handler/server75.rb +26 -0
- data/lib/websocket/handshake/handler/server76.rb +71 -0
- data/lib/websocket/handshake/server.rb +56 -0
- data/lib/websocket/version.rb +3 -0
- data/spec/frame/incoming_03_spec.rb +117 -0
- data/spec/frame/incoming_04_spec.rb +117 -0
- data/spec/frame/incoming_05_spec.rb +133 -0
- data/spec/frame/incoming_07_spec.rb +133 -0
- data/spec/frame/incoming_75_spec.rb +59 -0
- data/spec/frame/incoming_common_spec.rb +22 -0
- data/spec/frame/outgoing_03_spec.rb +77 -0
- data/spec/frame/outgoing_04_spec.rb +77 -0
- data/spec/frame/outgoing_05_spec.rb +77 -0
- data/spec/frame/outgoing_07_spec.rb +77 -0
- data/spec/frame/outgoing_75_spec.rb +41 -0
- data/spec/frame/outgoing_common_spec.rb +15 -0
- data/spec/handshake/client_04_spec.rb +20 -0
- data/spec/handshake/client_75_spec.rb +11 -0
- data/spec/handshake/client_76_spec.rb +20 -0
- data/spec/handshake/server_04_spec.rb +18 -0
- data/spec/handshake/server_75_spec.rb +11 -0
- data/spec/handshake/server_76_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/all_client_drafts.rb +77 -0
- data/spec/support/all_server_drafts.rb +86 -0
- data/spec/support/handshake_requests.rb +72 -0
- data/spec/support/incoming_frames.rb +30 -0
- data/spec/support/outgoing_frames.rb +14 -0
- data/websocket.gemspec +21 -0
- metadata +163 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Frame
|
5
|
+
module Handler
|
6
|
+
module Handler04
|
7
|
+
|
8
|
+
include Handler03
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# The only difference between draft 03 framing and draft 04 framing is
|
13
|
+
# that the MORE bit has been changed to a FIN bit
|
14
|
+
def fin; true; end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Frame
|
5
|
+
module Handler
|
6
|
+
module Handler07
|
7
|
+
|
8
|
+
include Handler05
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
FRAME_TYPES = {
|
13
|
+
:continuation => 0,
|
14
|
+
:text => 1,
|
15
|
+
:binary => 2,
|
16
|
+
:close => 8,
|
17
|
+
:ping => 9,
|
18
|
+
:pong => 10,
|
19
|
+
}
|
20
|
+
|
21
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
22
|
+
|
23
|
+
def type_to_opcode(frame_type)
|
24
|
+
FRAME_TYPES[frame_type] || raise(WebSocket::Error, :unknown_frame_type)
|
25
|
+
end
|
26
|
+
|
27
|
+
def opcode_to_type(opcode)
|
28
|
+
FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error, :unknown_opcode)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Frame
|
5
|
+
module Handler
|
6
|
+
module Handler75
|
7
|
+
|
8
|
+
include Base
|
9
|
+
|
10
|
+
def supported_frames
|
11
|
+
[:text, :close]
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def encode_frame
|
17
|
+
case @type
|
18
|
+
when :close then "\xff\x00"
|
19
|
+
when :text then
|
20
|
+
ary = ["\x00", @data, "\xff"]
|
21
|
+
ary.collect{ |s| s.encode('UTF-8', 'UTF-8', :invalid => :replace) if s.respond_to?(:encode) }
|
22
|
+
ary.join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode_frame
|
27
|
+
return if @data.size == 0
|
28
|
+
|
29
|
+
pointer = 0
|
30
|
+
frame_type = @data.getbyte(pointer)
|
31
|
+
pointer += 1
|
32
|
+
|
33
|
+
if (frame_type & 0x80) == 0x80
|
34
|
+
# If the high-order bit of the /frame type/ byte is set
|
35
|
+
length = 0
|
36
|
+
|
37
|
+
loop do
|
38
|
+
return if !@data.getbyte(pointer)
|
39
|
+
b = @data.getbyte(pointer)
|
40
|
+
pointer += 1
|
41
|
+
b_v = b & 0x7F
|
42
|
+
length = length * 128 + b_v
|
43
|
+
break unless (b & 0x80) == 0x80
|
44
|
+
end
|
45
|
+
|
46
|
+
set_error(:frame_too_long) and return if length > MAX_FRAME_SIZE
|
47
|
+
|
48
|
+
unless @data.getbyte(pointer+length-1) == nil
|
49
|
+
# Straight from spec - I'm sure this isn't crazy...
|
50
|
+
# 6. Read /length/ bytes.
|
51
|
+
# 7. Discard the read bytes.
|
52
|
+
@data = @data[(pointer+length)..-1]
|
53
|
+
|
54
|
+
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
55
|
+
if length == 0
|
56
|
+
self.class.new(:version => version, :type => :close, :decoded => true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
# If the high-order bit of the /frame type/ byte is _not_ set
|
61
|
+
|
62
|
+
set_error(:invalid_frame) and return if @data.getbyte(0) != 0x00
|
63
|
+
|
64
|
+
# Addition to the spec to protect against malicious requests
|
65
|
+
set_error(:frame_too_long) and return if @data.size > MAX_FRAME_SIZE
|
66
|
+
|
67
|
+
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
68
|
+
if msg
|
69
|
+
msg.gsub!(/\A\x00|\xff\z/, '')
|
70
|
+
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
71
|
+
self.class.new(:version => version, :type => :text, :data => msg, :decoded => true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Frame
|
3
|
+
class Incoming < Base
|
4
|
+
|
5
|
+
autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/incoming/client"
|
6
|
+
autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/incoming/server"
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
@decoded = args[:decoded] || false
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
# If data is still encoded after receiving then this is false. After calling "next" you will receive
|
14
|
+
# another instance of incoming frame, but with data decoded - this function will return true and
|
15
|
+
# to_s will return frame content instead of raw data.
|
16
|
+
# @return [Boolean] If frame already decoded?
|
17
|
+
def decoded?
|
18
|
+
@decoded
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add provided string as raw incoming frame.
|
22
|
+
# @param data [String] Raw frame
|
23
|
+
def <<(data)
|
24
|
+
@data << data
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return next complete frame.
|
28
|
+
# This function will merge together splitted frames and return as combined content.
|
29
|
+
# Check #error if nil received to check for eventual parsing errors
|
30
|
+
# @return [WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
|
31
|
+
def next
|
32
|
+
decode_frame unless decoded?
|
33
|
+
end
|
34
|
+
|
35
|
+
# If decoded then this will return frame content. Otherwise it will return raw frame.
|
36
|
+
# @return [String] Data of frame
|
37
|
+
def to_s
|
38
|
+
@data
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Frame
|
3
|
+
class Outgoing < Base
|
4
|
+
|
5
|
+
autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/outgoing/client"
|
6
|
+
autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/outgoing/server"
|
7
|
+
|
8
|
+
# Is selected type supported by current draft version?
|
9
|
+
# @return [Boolean] true if frame type is supported
|
10
|
+
def supported?
|
11
|
+
support_type?
|
12
|
+
end
|
13
|
+
|
14
|
+
# Should current frame be sent? Exclude empty frames etc.
|
15
|
+
# @return [Boolean] true if frame should be sent
|
16
|
+
def require_sending?
|
17
|
+
!error?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return raw frame formatted for sending.
|
21
|
+
def to_s
|
22
|
+
set_error(:unknown_frame_type) and return unless supported?
|
23
|
+
encode_frame
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
|
4
|
+
autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/base"
|
5
|
+
autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/client"
|
6
|
+
autoload :Handler, "#{::WebSocket::ROOT}/websocket/handshake/handler"
|
7
|
+
autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/server"
|
8
|
+
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :host, :port, :path, :query,
|
6
|
+
:error, :state, :version, :secure
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
@state = :new
|
10
|
+
|
11
|
+
@data = ""
|
12
|
+
@headers = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(data)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def finished?
|
20
|
+
@state == :finished || @state == :error
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
finished? && @error == nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def should_respond?
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def leftovers
|
32
|
+
@leftovers.split("\n", reserved_leftover_lines + 1)[reserved_leftover_lines]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def reserved_leftover_lines
|
38
|
+
0
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_error(message)
|
42
|
+
@state = :error
|
43
|
+
@error = message
|
44
|
+
end
|
45
|
+
|
46
|
+
HEADER = /^([^:]+):\s*(.+)$/
|
47
|
+
|
48
|
+
def parse_data
|
49
|
+
header, @leftovers = @data.split("\r\n\r\n", 2)
|
50
|
+
return unless @leftovers # The whole header has not been received yet.
|
51
|
+
|
52
|
+
lines = header.split("\r\n")
|
53
|
+
|
54
|
+
first_line = lines.shift
|
55
|
+
return unless parse_first_line(first_line)
|
56
|
+
|
57
|
+
lines.each do |line|
|
58
|
+
h = HEADER.match(line)
|
59
|
+
@headers[h[1].strip.downcase] = h[2].strip if h
|
60
|
+
end
|
61
|
+
|
62
|
+
@state = :finished
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'URI' unless defined?(URI)
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Handshake
|
5
|
+
class Client < Base
|
6
|
+
|
7
|
+
def initialize(args = {})
|
8
|
+
super
|
9
|
+
|
10
|
+
@version = args[:version] || DEFAULT_VERSION
|
11
|
+
@origin = args[:origin]
|
12
|
+
|
13
|
+
if args[:uri]
|
14
|
+
uri = URI.parse(args[:uri])
|
15
|
+
@secure = (uri.scheme == 'wss')
|
16
|
+
@host = uri.host
|
17
|
+
@port = uri.port
|
18
|
+
@path = uri.path
|
19
|
+
@query = uri.query
|
20
|
+
end
|
21
|
+
|
22
|
+
@secure = args[:secure] if args[:secure]
|
23
|
+
@host = args[:host] if args[:host]
|
24
|
+
@port = args[:port] if args[:port]
|
25
|
+
@path = args[:path] if args[:path]
|
26
|
+
@query = args[:query] if args[:query]
|
27
|
+
|
28
|
+
@path ||= '/'
|
29
|
+
|
30
|
+
set_error(:no_host_provided) unless @host
|
31
|
+
|
32
|
+
include_version
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(data)
|
36
|
+
@data << data
|
37
|
+
if parse_data
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def should_respond?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def uri
|
47
|
+
uri = @secure ? "wss://" : "ws://"
|
48
|
+
uri << @host
|
49
|
+
uri << ":#{@port}" if @port
|
50
|
+
uri << @path
|
51
|
+
uri << "?#{@query}" if @query
|
52
|
+
uri
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def include_version
|
58
|
+
case @version
|
59
|
+
when 75 then extend Handler::Client75
|
60
|
+
when 76, 0 then extend Handler::Client76
|
61
|
+
when 1..3 then extend Handler::Client01
|
62
|
+
when 4..13 then extend Handler::Client04
|
63
|
+
else set_error(:unknown_protocol_version) and return false
|
64
|
+
end
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
FIRST_LINE = /^HTTP\/1\.1 (\d{3})[\w\s]*$/
|
69
|
+
|
70
|
+
def parse_first_line(line)
|
71
|
+
line_parts = line.match(FIRST_LINE)
|
72
|
+
status = line_parts[1]
|
73
|
+
set_error(:invalid_status_code) and return unless status == '101'
|
74
|
+
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
module Handler
|
4
|
+
|
5
|
+
autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/handler/base"
|
6
|
+
|
7
|
+
autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/handler/client"
|
8
|
+
autoload :Client01, "#{::WebSocket::ROOT}/websocket/handshake/handler/client01"
|
9
|
+
autoload :Client04, "#{::WebSocket::ROOT}/websocket/handshake/handler/client04"
|
10
|
+
autoload :Client75, "#{::WebSocket::ROOT}/websocket/handshake/handler/client75"
|
11
|
+
autoload :Client76, "#{::WebSocket::ROOT}/websocket/handshake/handler/client76"
|
12
|
+
|
13
|
+
autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/handler/server"
|
14
|
+
autoload :Server04, "#{::WebSocket::ROOT}/websocket/handshake/handler/server04"
|
15
|
+
autoload :Server75, "#{::WebSocket::ROOT}/websocket/handshake/handler/server75"
|
16
|
+
autoload :Server76, "#{::WebSocket::ROOT}/websocket/handshake/handler/server76"
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|