websocket 1.0.7 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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