websocket 1.0.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 +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
|