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.
Files changed (73) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +11 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +5 -0
  5. data/README.md +117 -0
  6. data/Rakefile +23 -0
  7. data/autobahn-client.json +11 -0
  8. data/autobahn-server.json +10 -0
  9. data/examples/base.rb +162 -0
  10. data/examples/client.rb +70 -0
  11. data/examples/server.rb +56 -0
  12. data/examples/tests/autobahn_client.rb +52 -0
  13. data/examples/tests/echo_client.rb +42 -0
  14. data/examples/tests/echo_server.rb +45 -0
  15. data/lib/websocket.rb +16 -0
  16. data/lib/websocket/frame.rb +11 -0
  17. data/lib/websocket/frame/base.rb +45 -0
  18. data/lib/websocket/frame/data.rb +52 -0
  19. data/lib/websocket/frame/handler.rb +15 -0
  20. data/lib/websocket/frame/handler/base.rb +41 -0
  21. data/lib/websocket/frame/handler/handler03.rb +162 -0
  22. data/lib/websocket/frame/handler/handler04.rb +19 -0
  23. data/lib/websocket/frame/handler/handler05.rb +17 -0
  24. data/lib/websocket/frame/handler/handler07.rb +34 -0
  25. data/lib/websocket/frame/handler/handler75.rb +79 -0
  26. data/lib/websocket/frame/incoming.rb +43 -0
  27. data/lib/websocket/frame/incoming/client.rb +9 -0
  28. data/lib/websocket/frame/incoming/server.rb +9 -0
  29. data/lib/websocket/frame/outgoing.rb +28 -0
  30. data/lib/websocket/frame/outgoing/client.rb +9 -0
  31. data/lib/websocket/frame/outgoing/server.rb +9 -0
  32. data/lib/websocket/handshake.rb +10 -0
  33. data/lib/websocket/handshake/base.rb +67 -0
  34. data/lib/websocket/handshake/client.rb +80 -0
  35. data/lib/websocket/handshake/handler.rb +20 -0
  36. data/lib/websocket/handshake/handler/base.rb +25 -0
  37. data/lib/websocket/handshake/handler/client.rb +19 -0
  38. data/lib/websocket/handshake/handler/client01.rb +19 -0
  39. data/lib/websocket/handshake/handler/client04.rb +47 -0
  40. data/lib/websocket/handshake/handler/client75.rb +25 -0
  41. data/lib/websocket/handshake/handler/client76.rb +85 -0
  42. data/lib/websocket/handshake/handler/server.rb +30 -0
  43. data/lib/websocket/handshake/handler/server04.rb +38 -0
  44. data/lib/websocket/handshake/handler/server75.rb +26 -0
  45. data/lib/websocket/handshake/handler/server76.rb +71 -0
  46. data/lib/websocket/handshake/server.rb +56 -0
  47. data/lib/websocket/version.rb +3 -0
  48. data/spec/frame/incoming_03_spec.rb +117 -0
  49. data/spec/frame/incoming_04_spec.rb +117 -0
  50. data/spec/frame/incoming_05_spec.rb +133 -0
  51. data/spec/frame/incoming_07_spec.rb +133 -0
  52. data/spec/frame/incoming_75_spec.rb +59 -0
  53. data/spec/frame/incoming_common_spec.rb +22 -0
  54. data/spec/frame/outgoing_03_spec.rb +77 -0
  55. data/spec/frame/outgoing_04_spec.rb +77 -0
  56. data/spec/frame/outgoing_05_spec.rb +77 -0
  57. data/spec/frame/outgoing_07_spec.rb +77 -0
  58. data/spec/frame/outgoing_75_spec.rb +41 -0
  59. data/spec/frame/outgoing_common_spec.rb +15 -0
  60. data/spec/handshake/client_04_spec.rb +20 -0
  61. data/spec/handshake/client_75_spec.rb +11 -0
  62. data/spec/handshake/client_76_spec.rb +20 -0
  63. data/spec/handshake/server_04_spec.rb +18 -0
  64. data/spec/handshake/server_75_spec.rb +11 -0
  65. data/spec/handshake/server_76_spec.rb +46 -0
  66. data/spec/spec_helper.rb +4 -0
  67. data/spec/support/all_client_drafts.rb +77 -0
  68. data/spec/support/all_server_drafts.rb +86 -0
  69. data/spec/support/handshake_requests.rb +72 -0
  70. data/spec/support/incoming_frames.rb +30 -0
  71. data/spec/support/outgoing_frames.rb +14 -0
  72. data/websocket.gemspec +21 -0
  73. 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,17 @@
1
+ # encoding: binary
2
+
3
+ module WebSocket
4
+ module Frame
5
+ module Handler
6
+ module Handler05
7
+
8
+ include Handler04
9
+
10
+ private
11
+
12
+ def masking?; true; end
13
+
14
+ end
15
+ end
16
+ end
17
+ 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,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Incoming
4
+ class Client < Incoming
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Incoming
4
+ class Server < Incoming
5
+
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Outgoing
4
+ class Client < Outgoing
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Outgoing
4
+ class Server < Outgoing
5
+
6
+ end
7
+ end
8
+ end
9
+ 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