websocket 1.0.7 → 1.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.
Files changed (48) hide show
  1. data/CHANGELOG.md +8 -0
  2. data/Gemfile +1 -0
  3. data/README.md +1 -0
  4. data/lib/websocket.rb +15 -4
  5. data/lib/websocket/error.rb +85 -0
  6. data/lib/websocket/exception_handler.rb +36 -0
  7. data/lib/websocket/frame/base.rb +13 -16
  8. data/lib/websocket/frame/data.rb +1 -1
  9. data/lib/websocket/frame/handler/base.rb +6 -2
  10. data/lib/websocket/frame/handler/handler03.rb +49 -55
  11. data/lib/websocket/frame/handler/handler04.rb +1 -3
  12. data/lib/websocket/frame/handler/handler05.rb +2 -6
  13. data/lib/websocket/frame/handler/handler07.rb +5 -7
  14. data/lib/websocket/frame/handler/handler75.rb +15 -19
  15. data/lib/websocket/frame/incoming.rb +2 -1
  16. data/lib/websocket/frame/incoming/client.rb +1 -3
  17. data/lib/websocket/frame/incoming/server.rb +1 -3
  18. data/lib/websocket/frame/outgoing.rb +3 -2
  19. data/lib/websocket/frame/outgoing/client.rb +1 -3
  20. data/lib/websocket/frame/outgoing/server.rb +1 -3
  21. data/lib/websocket/handshake/base.rb +9 -5
  22. data/lib/websocket/handshake/client.rb +13 -12
  23. data/lib/websocket/handshake/handler/base.rb +10 -2
  24. data/lib/websocket/handshake/handler/client.rb +3 -5
  25. data/lib/websocket/handshake/handler/client01.rb +2 -4
  26. data/lib/websocket/handshake/handler/client04.rb +6 -8
  27. data/lib/websocket/handshake/handler/client75.rb +4 -6
  28. data/lib/websocket/handshake/handler/client76.rb +3 -5
  29. data/lib/websocket/handshake/handler/server.rb +1 -3
  30. data/lib/websocket/handshake/handler/server04.rb +7 -5
  31. data/lib/websocket/handshake/handler/server75.rb +3 -5
  32. data/lib/websocket/handshake/handler/server76.rb +9 -11
  33. data/lib/websocket/handshake/server.rb +49 -10
  34. data/lib/websocket/version.rb +1 -1
  35. data/spec/frame/incoming_03_spec.rb +3 -3
  36. data/spec/frame/incoming_04_spec.rb +3 -3
  37. data/spec/frame/incoming_05_spec.rb +3 -3
  38. data/spec/frame/incoming_07_spec.rb +3 -3
  39. data/spec/frame/incoming_75_spec.rb +2 -2
  40. data/spec/handshake/client_04_spec.rb +3 -3
  41. data/spec/handshake/client_76_spec.rb +3 -3
  42. data/spec/spec_helper.rb +1 -0
  43. data/spec/support/all_server_drafts.rb +33 -0
  44. data/spec/support/frames_base.rb +1 -3
  45. data/spec/support/incoming_frames.rb +26 -1
  46. data/spec/support/overwrites.rb +9 -0
  47. data/websocket.gemspec +0 -2
  48. metadata +29 -53
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0
4
+
5
+ - allow raising ruby errors instead of setting `error` flag
6
+ - allow access to handshake headers
7
+ - add from_rack method
8
+ - add from_hash method
9
+ - stop extending handlers - it should improve performance for opening connection
10
+
3
11
  ## 1.0.7
4
12
 
5
13
  - fix requiring url under Ruby 1.9.1
data/Gemfile CHANGED
@@ -3,3 +3,4 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rake'
6
+ gem 'rspec', '~> 2.11'
data/README.md CHANGED
@@ -104,6 +104,7 @@ frame.next # "world!""
104
104
 
105
105
  - [WebSocket-EventMachine-Client](https://github.com/imanel/websocket-eventmachine-client) - client based on EventMachine
106
106
  - [WebSocket-EventMachine-Server](https://github.com/imanel/websocket-eventmachine-server) - server based on EventMachine (drop-in replacement for EM-WebSocket)
107
+ - [Selenium-WebDriver](https://rubygems.org/gems/selenium-webdriver) - tool for writing automated tests of websites
107
108
  - [Rubame](https://github.com/saward/Rubame) - websocket game server
108
109
 
109
110
  ## Native extension
data/lib/websocket.rb CHANGED
@@ -4,15 +4,15 @@
4
4
  # @author Bernard "Imanel" Potocki
5
5
  # @see http://github.com/imanel/websocket-ruby main repository
6
6
  module WebSocket
7
- class Error < RuntimeError; end
8
7
 
9
8
  # Default WebSocket version to use
10
9
  DEFAULT_VERSION = 13
11
10
  ROOT = File.expand_path(File.dirname(__FILE__))
12
11
 
13
- autoload :Frame, "#{ROOT}/websocket/frame"
14
- autoload :Handler, "#{ROOT}/websocket/handler"
15
- autoload :Handshake, "#{ROOT}/websocket/handshake"
12
+ autoload :Error, "#{ROOT}/websocket/error"
13
+ autoload :ExceptionHandler, "#{ROOT}/websocket/exception_handler"
14
+ autoload :Frame, "#{ROOT}/websocket/frame"
15
+ autoload :Handshake, "#{ROOT}/websocket/handshake"
16
16
 
17
17
  # Limit of frame size payload in bytes
18
18
  def self.max_frame_size
@@ -24,6 +24,17 @@ module WebSocket
24
24
  @max_frame_size = val
25
25
  end
26
26
 
27
+ # If set to true error will be raised instead of setting `error` method.
28
+ # All errors inherit from WebSocket::Error.
29
+ def self.should_raise
30
+ @should_raise ||= false
31
+ end
32
+
33
+ # Should protocol errors raise ruby errors? If false then `error` flag is set instead.
34
+ def self.should_raise=(val)
35
+ @should_raise = val
36
+ end
37
+
27
38
  end
28
39
 
29
40
  # Try loading websocket-native if available
@@ -0,0 +1,85 @@
1
+ module WebSocket
2
+ class Error < RuntimeError
3
+
4
+ class Frame < ::WebSocket::Error
5
+
6
+ class ControlFramePayloadTooLong < ::WebSocket::Error::Frame
7
+ def message; :control_frame_payload_too_long; end
8
+ end
9
+
10
+ class DataFrameInsteadContinuation < ::WebSocket::Error::Frame
11
+ def message; :data_frame_instead_continuation; end
12
+ end
13
+
14
+ class FragmentedControlFrame < ::WebSocket::Error::Frame
15
+ def message; :fragmented_control_frame; end
16
+ end
17
+
18
+ class Invalid < ::WebSocket::Error::Frame
19
+ def message; :invalid_frame; end
20
+ end
21
+
22
+ class InvalidPayloadEncoding < ::WebSocket::Error::Frame
23
+ def message; :invalid_payload_encoding; end
24
+ end
25
+
26
+ class MaskTooShort < ::WebSocket::Error::Frame
27
+ def message; :mask_is_too_short; end
28
+ end
29
+
30
+ class ReservedBitUsed < ::WebSocket::Error::Frame
31
+ def message; :reserved_bit_used; end
32
+ end
33
+
34
+ class TooLong < ::WebSocket::Error::Frame
35
+ def message; :frame_too_long; end
36
+ end
37
+
38
+ class UnexpectedContinuationFrame < ::WebSocket::Error::Frame
39
+ def message; :unexpected_continuation_frame; end
40
+ end
41
+
42
+ class UnknownFrameType < ::WebSocket::Error::Frame
43
+ def message; :unknown_frame_type; end
44
+ end
45
+
46
+ class UnknownOpcode < ::WebSocket::Error::Frame
47
+ def message; :unknown_opcode; end
48
+ end
49
+
50
+ class UnknownVersion < ::WebSocket::Error::Frame
51
+ def message; :unknown_protocol_version; end
52
+ end
53
+
54
+ end
55
+
56
+ class Handshake < ::WebSocket::Error
57
+
58
+ class GetRequestRequired < ::WebSocket::Error::Handshake
59
+ def message; :get_request_required; end
60
+ end
61
+
62
+ class InvalidAuthentication < ::WebSocket::Error::Handshake
63
+ def message; :invalid_handshake_authentication; end
64
+ end
65
+
66
+ class InvalidHeader < ::WebSocket::Error::Handshake
67
+ def message; :invalid_header; end
68
+ end
69
+
70
+ class InvalidStatusCode < ::WebSocket::Error::Handshake
71
+ def message; :invalid_status_code; end
72
+ end
73
+
74
+ class NoHostProvided < ::WebSocket::Error::Handshake
75
+ def message; :no_host_provided; end
76
+ end
77
+
78
+ class UnknownVersion < ::WebSocket::Error::Handshake
79
+ def message; :unknown_protocol_version; end
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,36 @@
1
+ module WebSocket
2
+ module ExceptionHandler
3
+
4
+ attr_reader :error
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ private
11
+
12
+ # Changes state to error and sets error message
13
+ # @param [String] message Error message to set
14
+ def set_error(message)
15
+ @error = message
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ def rescue_method(method_name, options = {})
21
+ define_method "#{method_name}_with_rescue" do |*args|
22
+ begin
23
+ send("#{method_name}_without_rescue", *args)
24
+ rescue WebSocket::Error => e
25
+ set_error(e.message.to_sym)
26
+ WebSocket.should_raise ? raise : options[:return]
27
+ end
28
+ end
29
+ alias_method "#{method_name}_without_rescue", method_name
30
+ alias_method method_name, "#{method_name}_with_rescue"
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -2,8 +2,9 @@ module WebSocket
2
2
  module Frame
3
3
  # @abstract Subclass and override to implement custom frames
4
4
  class Base
5
+ include ExceptionHandler
5
6
 
6
- attr_reader :data, :type, :version, :error
7
+ attr_reader :data, :type, :version
7
8
 
8
9
  # Initialize frame
9
10
  # @param args [Hash] Arguments for frame
@@ -16,8 +17,10 @@ module WebSocket
16
17
  @code = args[:code]
17
18
  @data = Data.new(args[:data].to_s)
18
19
  @version = args[:version] || DEFAULT_VERSION
20
+ @handler = nil
19
21
  include_version
20
22
  end
23
+ rescue_method :initialize
21
24
 
22
25
  # Check if some errors occured
23
26
  # @return [Boolean] True if error is set
@@ -27,7 +30,7 @@ module WebSocket
27
30
 
28
31
  # Is selected type supported for selected handler?
29
32
  def support_type?
30
- supported_frames.include?(@type)
33
+ @handler.supported_frames.include?(@type)
31
34
  end
32
35
 
33
36
  # Implement in submodules
@@ -47,23 +50,17 @@ module WebSocket
47
50
  # Include set of methods for selected protocol version
48
51
  # @return [Boolean] false if protocol number is unknown, otherwise true
49
52
  def include_version
50
- case @version
51
- when 75..76 then extend Handler::Handler75
52
- when 0..2 then extend Handler::Handler75
53
- when 3 then extend Handler::Handler03
54
- when 4 then extend Handler::Handler04
55
- when 5..6 then extend Handler::Handler05
56
- when 7..13 then extend Handler::Handler07
57
- else set_error(:unknown_protocol_version) and return
53
+ @handler = case @version
54
+ when 75..76 then Handler::Handler75.new(self)
55
+ when 0..2 then Handler::Handler75.new(self)
56
+ when 3 then Handler::Handler03.new(self)
57
+ when 4 then Handler::Handler04.new(self)
58
+ when 5..6 then Handler::Handler05.new(self)
59
+ when 7..13 then Handler::Handler07.new(self)
60
+ else raise WebSocket::Error::Frame::UnknownVersion
58
61
  end
59
62
  end
60
63
 
61
- # Changes state to error and sets error message
62
- # @param [String] message Error message to set
63
- def set_error(message)
64
- @error = message
65
- end
66
-
67
64
  end
68
65
  end
69
66
  end
@@ -11,7 +11,7 @@ module WebSocket
11
11
  end
12
12
 
13
13
  def set_mask
14
- raise "Too short" if bytesize < 4 # TODO - change
14
+ raise WebSocket::Error::Frame::MaskTooShort if bytesize < 4
15
15
  @masking_key = self[0..3].bytes.to_a
16
16
  end
17
17
 
@@ -1,9 +1,11 @@
1
1
  module WebSocket
2
2
  module Frame
3
3
  module Handler
4
- module Base
4
+ class Base
5
5
 
6
- private
6
+ def initialize(frame)
7
+ @frame = frame
8
+ end
7
9
 
8
10
  # Convert data to raw frame ready to send to client
9
11
  # @return [String] Encoded frame
@@ -17,6 +19,8 @@ module WebSocket
17
19
  raise NotImplementedError
18
20
  end
19
21
 
22
+ private
23
+
20
24
  # Check if frame is one of control frames
21
25
  # @param [Symbol] frame_type Frame type
22
26
  # @return [Boolean] True if given frame type is control frame
@@ -3,28 +3,37 @@
3
3
  module WebSocket
4
4
  module Frame
5
5
  module Handler
6
- module Handler03
6
+ class Handler03 < Base
7
7
 
8
- include Base
8
+ # Hash of frame names and it's opcodes
9
+ FRAME_TYPES = {
10
+ :continuation => 0,
11
+ :close => 1,
12
+ :ping => 2,
13
+ :pong => 3,
14
+ :text => 4,
15
+ :binary => 5
16
+ }
17
+
18
+ # Hash of frame opcodes and it's names
19
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert
9
20
 
10
21
  # @see WebSocket::Frame::Base#supported_frames
11
22
  def supported_frames
12
23
  [:text, :binary, :close, :ping, :pong]
13
24
  end
14
25
 
15
- private
16
-
17
26
  # @see WebSocket::Frame::Handler::Base#encode_frame
18
27
  def encode_frame
19
28
  frame = ''
20
29
 
21
- opcode = type_to_opcode(@type)
30
+ opcode = type_to_opcode(@frame.type)
22
31
  byte1 = opcode | (fin ? 0b10000000 : 0b00000000) # since more, rsv1-3 are 0 and 0x80 for Draft 4
23
32
  frame << byte1
24
33
 
25
- mask = outgoing_masking? ? 0b10000000 : 0b00000000
34
+ mask = @frame.outgoing_masking? ? 0b10000000 : 0b00000000
26
35
 
27
- length = @data.size
36
+ length = @frame.data.size
28
37
  if length <= 125
29
38
  byte2 = length # since rsv4 is 0
30
39
  frame << (byte2 | mask)
@@ -36,58 +45,56 @@ module WebSocket
36
45
  frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
37
46
  end
38
47
 
39
- if outgoing_masking?
48
+ if @frame.outgoing_masking?
40
49
  masking_key = [rand(256).chr, rand(256).chr, rand(256).chr, rand(256).chr].join
41
- tmp_data = Data.new([masking_key, @data.to_s].join)
50
+ tmp_data = Data.new([masking_key, @frame.data.to_s].join)
42
51
  tmp_data.set_mask
43
52
  frame << masking_key + tmp_data.getbytes(4, tmp_data.size)
44
53
  else
45
- frame << @data
54
+ frame << @frame.data
46
55
  end
47
56
 
48
57
  frame
49
- rescue WebSocket::Error => e
50
- set_error(e.message.to_sym) and return
51
58
  end
52
59
 
53
60
  # @see WebSocket::Frame::Handler::Base#decode_frame
54
61
  def decode_frame
55
- while @data.size > 1
62
+ while @frame.data.size > 1
56
63
  pointer = 0
57
64
 
58
- more = ((@data.getbyte(pointer) & 0b10000000) == 0b10000000) ^ fin
65
+ more = ((@frame.data.getbyte(pointer) & 0b10000000) == 0b10000000) ^ fin
59
66
 
60
- raise(WebSocket::Error, :reserved_bit_used) if @data.getbyte(pointer) & 0b01110000 != 0b00000000
67
+ raise(WebSocket::Error::Frame::ReservedBitUsed) if @frame.data.getbyte(pointer) & 0b01110000 != 0b00000000
61
68
 
62
- opcode = @data.getbyte(pointer) & 0b00001111
69
+ opcode = @frame.data.getbyte(pointer) & 0b00001111
63
70
  frame_type = opcode_to_type(opcode)
64
71
  pointer += 1
65
72
 
66
- raise(WebSocket::Error, :fragmented_control_frame) if more && control_frame?(frame_type)
67
- raise(WebSocket::Error, :data_frame_instead_continuation) if data_frame?(frame_type) && !@application_data_buffer.nil?
73
+ raise(WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type)
74
+ raise(WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil?
68
75
 
69
- mask = incoming_masking? && (@data.getbyte(pointer) & 0b10000000) == 0b10000000
70
- length = @data.getbyte(pointer) & 0b01111111
76
+ mask = @frame.incoming_masking? && (@frame.data.getbyte(pointer) & 0b10000000) == 0b10000000
77
+ length = @frame.data.getbyte(pointer) & 0b01111111
71
78
 
72
- raise(WebSocket::Error, :control_frame_payload_too_long) if length > 125 && control_frame?(frame_type)
79
+ raise(WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type)
73
80
 
74
81
  pointer += 1
75
82
 
76
83
  payload_length = case length
77
84
  when 127 # Length defined by 8 bytes
78
85
  # Check buffer size
79
- return if @data.getbyte(pointer+8-1) == nil # Buffer incomplete
86
+ return if @frame.data.getbyte(pointer+8-1) == nil # Buffer incomplete
80
87
 
81
88
  # Only using the last 4 bytes for now, till I work out how to
82
89
  # unpack 8 bytes. I'm sure 4GB frames will do for now :)
83
- l = @data.getbytes(pointer+4, 4).unpack('N').first
90
+ l = @frame.data.getbytes(pointer+4, 4).unpack('N').first
84
91
  pointer += 8
85
92
  l
86
93
  when 126 # Length defined by 2 bytes
87
94
  # Check buffer size
88
- return if @data.getbyte(pointer+2-1) == nil # Buffer incomplete
95
+ return if @frame.data.getbyte(pointer+2-1) == nil # Buffer incomplete
89
96
 
90
- l = @data.getbytes(pointer, 2).unpack('n').first
97
+ l = @frame.data.getbytes(pointer, 2).unpack('n').first
91
98
  pointer += 2
92
99
  l
93
100
  else
@@ -98,27 +105,27 @@ module WebSocket
98
105
  frame_length = pointer + payload_length
99
106
  frame_length += 4 if mask
100
107
 
101
- raise(WebSocket::Error, :frame_too_long) if frame_length > WebSocket.max_frame_size
108
+ raise(WebSocket::Error::Frame::TooLong) if frame_length > WebSocket.max_frame_size
102
109
 
103
110
  # Check buffer size
104
- return if @data.getbyte(frame_length-1) == nil # Buffer incomplete
111
+ return if @frame.data.getbyte(frame_length-1) == nil # Buffer incomplete
105
112
 
106
113
  # Remove frame header
107
- @data.slice!(0...pointer)
114
+ @frame.data.slice!(0...pointer)
108
115
  pointer = 0
109
116
 
110
117
  # Read application data (unmasked if required)
111
- @data.set_mask if mask
118
+ @frame.data.set_mask if mask
112
119
  pointer += 4 if mask
113
- application_data = @data.getbytes(pointer, payload_length)
120
+ application_data = @frame.data.getbytes(pointer, payload_length)
114
121
  application_data.force_encoding('UTF-8') if application_data.respond_to?(:force_encoding)
115
122
  pointer += payload_length
116
- @data.unset_mask if mask
123
+ @frame.data.unset_mask if mask
117
124
 
118
125
  # Throw away data up to pointer
119
- @data.slice!(0...pointer)
126
+ @frame.data.slice!(0...pointer)
120
127
 
121
- raise(WebSocket::Error, :unexpected_continuation_frame) if frame_type == :continuation && !@frame_type
128
+ raise(WebSocket::Error::Frame::UnexpectedContinuationFrame) if frame_type == :continuation && !@frame_type
122
129
 
123
130
  if more
124
131
  @application_data_buffer ||= ''
@@ -129,47 +136,34 @@ module WebSocket
129
136
  if frame_type == :continuation
130
137
  @application_data_buffer << application_data
131
138
  # Test valid UTF-8 encoding
132
- raise(WebSocket::Error, :invalid_payload_encoding) if @frame_type == :text && @application_data_buffer.respond_to?(:valid_encoding?) && !@application_data_buffer.valid_encoding?
133
- message = self.class.new(:version => version, :type => @frame_type, :data => @application_data_buffer, :decoded => true)
139
+ raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && @application_data_buffer.respond_to?(:valid_encoding?) && !@application_data_buffer.valid_encoding?
140
+ message = @frame.class.new(:version => @frame.version, :type => @frame_type, :data => @application_data_buffer, :decoded => true)
134
141
  @application_data_buffer = nil
135
142
  @frame_type = nil
136
143
  return message
137
144
  else
138
- raise(WebSocket::Error, :invalid_payload_encoding) if frame_type == :text && application_data.respond_to?(:valid_encoding?) && !application_data.valid_encoding?
139
- return self.class.new(:version => version, :type => frame_type, :data => application_data, :decoded => true)
145
+ raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && application_data.respond_to?(:valid_encoding?) && !application_data.valid_encoding?
146
+ return @frame.class.new(:version => @frame.version, :type => frame_type, :data => application_data, :decoded => true)
140
147
  end
141
148
  end
142
149
  end
143
150
  return nil
144
- rescue WebSocket::Error => e
145
- set_error(e.message.to_sym) and return
146
151
  end
147
152
 
148
- # This allows flipping the more bit to fin for draft 04
149
- def fin; false; end
150
-
151
153
  # Allow turning on or off masking
152
154
  def masking?; false; end
153
155
 
154
- # Hash of frame names and it's opcodes
155
- FRAME_TYPES = {
156
- :continuation => 0,
157
- :close => 1,
158
- :ping => 2,
159
- :pong => 3,
160
- :text => 4,
161
- :binary => 5
162
- }
156
+ private
163
157
 
164
- # Hash of frame opcodes and it's names
165
- FRAME_TYPES_INVERSE = FRAME_TYPES.invert
158
+ # This allows flipping the more bit to fin for draft 04
159
+ def fin; false; end
166
160
 
167
161
  # Convert frame type name to opcode
168
162
  # @param [Symbol] frame_type Frame type name
169
163
  # @return [Integer] opcode or nil
170
164
  # @raise [WebSocket::Error] if frame opcode is not known
171
165
  def type_to_opcode(frame_type)
172
- FRAME_TYPES[frame_type] || raise(WebSocket::Error, :unknown_frame_type)
166
+ FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
173
167
  end
174
168
 
175
169
  # Convert frame opcode to type name
@@ -177,7 +171,7 @@ module WebSocket
177
171
  # @return [Symbol] Frame type name or nil
178
172
  # @raise [WebSocket::Error] if frame type name is not known
179
173
  def opcode_to_type(opcode)
180
- FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error, :unknown_opcode)
174
+ FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
181
175
  end
182
176
 
183
177
  end