websocket-driver 0.4.0-java → 0.5.0-java

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 79be3fa7f1a4429fda1463f0ab0f809004b82593
4
- data.tar.gz: eed2e51b0a21fade327b48c4dab184afb92cc7dd
3
+ metadata.gz: bc15766e2fa0e536038e29fadff1132446bce140
4
+ data.tar.gz: ad665aaf5f8088712fd10524cb99ab8911013215
5
5
  SHA512:
6
- metadata.gz: 1f718f23af5483c2c30995c63b2d40a64d2077cc23fd1d0e34e551a9f7d06618883a7a45b5d884c4d334d1d7036263b93e52c8249bcee84146295fe8c8cd0506
7
- data.tar.gz: f0486b76187ee8a9d750621aac8498ac4dd4b4ab7f2f8d9a19e7af9b6c35532cab2ebdfd25b17ed38721eccb4d918bc8adc7c0c19f2eb70d914d34f79cd2bec4
6
+ metadata.gz: f11999301bb7c87b560543d2668841fe03ddcfd2545f49009096c707a5d266ffedbc3430450579a36218b4d09c8925923cf27607d45a215d2cfb71832473bb96
7
+ data.tar.gz: 9c79cd87ff2541ee12d0b953f9993dae2db27460a39bb197a4115f68f727479794ecd943d5ab7fb57293d2fac7ddf59ceda291355e42b5fdb80be8ec93f48a54
@@ -1,3 +1,7 @@
1
+ ### 0.5.0 / 2014-12-13
2
+
3
+ * Support protocol extensions via the websocket-extensions module
4
+
1
5
  ### 0.4.0 / 2014-11-08
2
6
 
3
7
  * Support connection via HTTP proxies using `CONNECT`
data/README.md CHANGED
@@ -14,6 +14,9 @@ hook this module up to some I/O object, it will do all of this for you:
14
14
  * Generate and send both server- and client-side handshakes
15
15
  * Recognize when the handshake phase completes and the WS protocol begins
16
16
  * Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
17
+ * Negotiate and use extensions via the
18
+ [websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
19
+ module
17
20
  * Buffer sent messages until the handshake process is finished
18
21
  * Deal with proxies that defer delivery of the draft-76 handshake body
19
22
  * Notify you when the socket is open and closed and when messages arrive
@@ -291,6 +294,13 @@ describing the error.
291
294
  Sets the callback block to execute when the socket becomes closed. The `event`
292
295
  object has `code` and `reason` attributes.
293
296
 
297
+ #### `driver.add_extension(extension)`
298
+
299
+ Registers a protocol extension whose operation will be negotiated via the
300
+ `Sec-WebSocket-Extensions` header. `extension` is any extension compatible with
301
+ the [websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
302
+ framework.
303
+
294
304
  #### `driver.set_header(name, value)`
295
305
 
296
306
  Sets a custom header to be sent as part of the handshake response, either from
@@ -2,10 +2,12 @@ require 'rubygems'
2
2
  require 'bundler/setup'
3
3
  require 'eventmachine'
4
4
  require 'websocket/driver'
5
+ require 'permessage_deflate'
5
6
 
6
7
  module Connection
7
8
  def initialize
8
9
  @driver = WebSocket::Driver.server(self)
10
+ @driver.add_extension(PermessageDeflate)
9
11
 
10
12
  @driver.on(:connect) { |e| @driver.start if WebSocket::Driver.websocket? @driver.env }
11
13
  @driver.on(:message) { |e| @driver.frame(e.data) }
@@ -4,11 +4,10 @@ import java.lang.Long;
4
4
  import java.io.IOException;
5
5
 
6
6
  import org.jruby.Ruby;
7
- import org.jruby.RubyArray;
8
7
  import org.jruby.RubyClass;
9
- import org.jruby.RubyFixnum;
10
8
  import org.jruby.RubyModule;
11
9
  import org.jruby.RubyObject;
10
+ import org.jruby.RubyString;
12
11
  import org.jruby.anno.JRubyMethod;
13
12
  import org.jruby.runtime.ObjectAllocator;
14
13
  import org.jruby.runtime.ThreadContext;
@@ -39,27 +38,18 @@ public class WebsocketMaskService implements BasicLibraryService {
39
38
 
40
39
  @JRubyMethod
41
40
  public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
42
- if (mask.isNil() || ((RubyArray)mask).getLength() == 0) {
43
- return payload;
44
- }
41
+ if (mask.isNil()) return payload;
45
42
 
46
- int n = ((RubyArray)payload).getLength(), i;
47
- long p, m;
48
- RubyArray unmasked = RubyArray.newArray(runtime, n);
43
+ byte[] payload_a = ((RubyString)payload).getBytes();
44
+ byte[] mask_a = ((RubyString)mask).getBytes();
45
+ int i, n = payload_a.length;
49
46
 
50
- long[] maskArray = {
51
- (Long)((RubyArray)mask).get(0),
52
- (Long)((RubyArray)mask).get(1),
53
- (Long)((RubyArray)mask).get(2),
54
- (Long)((RubyArray)mask).get(3)
55
- };
47
+ if (n == 0) return payload;
56
48
 
57
49
  for (i = 0; i < n; i++) {
58
- p = (Long)((RubyArray)payload).get(i);
59
- m = maskArray[i % 4];
60
- unmasked.set(i, p ^ m);
50
+ payload_a[i] ^= mask_a[i % 4];
61
51
  }
62
- return unmasked;
52
+ return RubyString.newStringNoCopy(runtime, payload_a);
63
53
  }
64
54
  }
65
55
  }
@@ -6,32 +6,36 @@ VALUE WebSocketMask = Qnil;
6
6
  void Init_websocket_mask();
7
7
  VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask);
8
8
 
9
- void Init_websocket_mask() {
9
+ void
10
+ Init_websocket_mask()
11
+ {
10
12
  WebSocket = rb_define_module("WebSocket");
11
13
  WebSocketMask = rb_define_module_under(WebSocket, "Mask");
12
14
  rb_define_singleton_method(WebSocketMask, "mask", method_websocket_mask, 2);
13
15
  }
14
16
 
15
- VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
16
- int n, i, p, m;
17
- int mask_array[4];
17
+ VALUE
18
+ method_websocket_mask(VALUE self,
19
+ VALUE payload,
20
+ VALUE mask)
21
+ {
22
+ char *payload_s, *mask_s, *unmasked_s;
23
+ int i, n;
18
24
  VALUE unmasked;
19
25
 
20
- if (mask == Qnil || RARRAY_LEN(mask) == 0) {
26
+ if (mask == Qnil || RSTRING_LEN(mask) != 4) {
21
27
  return payload;
22
28
  }
23
29
 
24
- n = RARRAY_LEN(payload);
25
- unmasked = rb_ary_new2(n);
30
+ payload_s = RSTRING_PTR(payload);
31
+ mask_s = RSTRING_PTR(mask);
32
+ n = RSTRING_LEN(payload);
26
33
 
27
- for (i = 0; i < 4; i++) {
28
- mask_array[i] = NUM2INT(rb_ary_entry(mask, i));
29
- }
34
+ unmasked = rb_str_new(0, n);
35
+ unmasked_s = RSTRING_PTR(unmasked);
30
36
 
31
37
  for (i = 0; i < n; i++) {
32
- p = NUM2INT(rb_ary_entry(payload, i));
33
- m = mask_array[i % 4];
34
- rb_ary_store(unmasked, i, INT2NUM(p ^ m));
38
+ unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
35
39
  }
36
40
  return unmasked;
37
41
  }
@@ -7,9 +7,11 @@
7
7
  require 'base64'
8
8
  require 'digest/md5'
9
9
  require 'digest/sha1'
10
+ require 'securerandom'
10
11
  require 'set'
11
12
  require 'stringio'
12
13
  require 'uri'
14
+ require 'websocket/extensions'
13
15
 
14
16
  module WebSocket
15
17
  autoload :HTTP, File.expand_path('../http', __FILE__)
@@ -73,6 +75,10 @@ module WebSocket
73
75
  STATES[@ready_state]
74
76
  end
75
77
 
78
+ def add_extension(extension)
79
+ false
80
+ end
81
+
76
82
  def set_header(name, value)
77
83
  return false unless @ready_state <= 0
78
84
  @headers[name] = value
@@ -139,29 +145,19 @@ module WebSocket
139
145
  end
140
146
 
141
147
  def self.encode(string, encoding = nil)
142
- if Array === string
143
- string = string.pack('C*')
144
- encoding ||= :binary
145
- else
146
- encoding ||= :utf8
147
- end
148
- case encoding
149
- when :binary
150
- string.force_encoding('ASCII-8BIT') if string.respond_to?(:force_encoding)
151
- when :utf8
152
- string.force_encoding('UTF-8') if string.respond_to?(:force_encoding)
153
- return nil unless valid_utf8?(string)
148
+ case string
149
+ when Array then
150
+ string = string.pack('C*')
151
+ encoding ||= :binary
152
+ when String then
153
+ encoding ||= :utf8
154
154
  end
155
+ encodings = {:utf8 => 'UTF-8', :binary => 'ASCII-8BIT'}
156
+ string.force_encoding(encodings[encoding]) if string.respond_to?(:force_encoding)
157
+ return nil if encoding == :utf8 and not valid_utf8?(string)
155
158
  string
156
159
  end
157
160
 
158
- def self.utf8_string(string)
159
- string = string.pack('C*') if Array === string
160
- string.respond_to?(:force_encoding) ?
161
- string.force_encoding('UTF-8') :
162
- string
163
- end
164
-
165
161
  def self.valid_utf8?(string)
166
162
  if defined?(UTF8_MATCH)
167
163
  UTF8_MATCH =~ string ? true : false
@@ -175,7 +171,7 @@ module WebSocket
175
171
  upgrade = env['HTTP_UPGRADE'] || ''
176
172
 
177
173
  env['REQUEST_METHOD'] == 'GET' and
178
- connection.downcase.split(/\s*,\s*/).include?('upgrade') and
174
+ connection.downcase.split(/ *, */).include?('upgrade') and
179
175
  upgrade.downcase == 'websocket'
180
176
  end
181
177
 
@@ -57,14 +57,21 @@ module WebSocket
57
57
 
58
58
  @http.parse(buffer)
59
59
  return fail_handshake('Invalid HTTP response') if @http.error?
60
+ return unless @http.complete?
60
61
 
61
- validate_handshake if @http.complete?
62
- parse(@http.body) if @ready_state == 1
62
+ validate_handshake
63
+ return if @ready_state == 3
64
+
65
+ open
66
+ parse(@http.body)
63
67
  end
64
68
 
65
69
  private
66
70
 
67
71
  def handshake_request
72
+ extensions = @extensions.generate_offer
73
+ @headers['Sec-WebSocket-Extensions'] = extensions if extensions
74
+
68
75
  start = "GET #{@pathname} HTTP/1.1"
69
76
  headers = [start, @headers.to_s, '']
70
77
  headers.join("\r\n")
@@ -114,7 +121,11 @@ module WebSocket
114
121
  end
115
122
  end
116
123
 
117
- open
124
+ begin
125
+ @extensions.activate(@headers['Sec-WebSocket-Extensions'])
126
+ rescue ::WebSocket::Extensions::ExtensionError => e
127
+ return fail_handshake(e.message)
128
+ end
118
129
  end
119
130
  end
120
131
 
@@ -25,9 +25,8 @@ module WebSocket
25
25
 
26
26
  def parse(buffer)
27
27
  return if @ready_state > 1
28
- buffer = buffer.bytes if buffer.respond_to?(:bytes)
29
28
 
30
- buffer.each do |data|
29
+ buffer.each_byte do |data|
31
30
  case @stage
32
31
  when -1 then
33
32
  @body << data
@@ -8,7 +8,7 @@ module WebSocket
8
8
  super
9
9
  input = @socket.env['rack.input']
10
10
  @stage = -1
11
- @body = input ? input.read.bytes.to_a : []
11
+ @body = Driver.encode(input ? input.read : '', :binary)
12
12
 
13
13
  @headers.clear
14
14
  @headers['Upgrade'] = 'WebSocket'
@@ -44,16 +44,12 @@ module WebSocket
44
44
  end
45
45
 
46
46
  def handshake_signature
47
- return nil unless @body.size >= BODY_SIZE
48
-
49
- head = @body[0...BODY_SIZE].pack('C*')
50
- head.force_encoding('ASCII-8BIT') if head.respond_to?(:force_encoding)
51
-
52
- env = @socket.env
47
+ return nil unless @body.bytesize >= BODY_SIZE
53
48
 
49
+ head = @body[0...BODY_SIZE]
50
+ env = @socket.env
54
51
  key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
55
52
  value1 = number_from_key(key1) / spaces_in_key(key1)
56
-
57
53
  key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
58
54
  value2 = number_from_key(key2) / spaces_in_key(key2)
59
55
 
@@ -67,7 +63,7 @@ module WebSocket
67
63
  @socket.write(Driver.encode(signature, :binary))
68
64
  @stage = 0
69
65
  open
70
- parse(@body[BODY_SIZE..-1]) if @body.size > BODY_SIZE
66
+ parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
71
67
  end
72
68
 
73
69
  def parse_leading_byte(data)
@@ -86,7 +82,7 @@ module WebSocket
86
82
  end
87
83
 
88
84
  def big_endian(number)
89
- string = ''
85
+ string = Driver.encode('', :binary)
90
86
  [24, 16, 8, 0].each do |offset|
91
87
  string << (number >> offset & 0xFF).chr
92
88
  end
@@ -3,6 +3,9 @@ module WebSocket
3
3
 
4
4
  class Hybi < Driver
5
5
  root = File.expand_path('../hybi', __FILE__)
6
+
7
+ autoload :Frame, root + '/frame'
8
+ autoload :Message, root + '/message'
6
9
  autoload :StreamReader, root + '/stream_reader'
7
10
 
8
11
  def self.generate_accept(key)
@@ -28,9 +31,9 @@ module WebSocket
28
31
  :pong => 10
29
32
  }
30
33
 
31
- OPCODE_CODES = OPCODES.values
32
- FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
33
- OPENING_OPCODES = OPCODES.values_at(:text, :binary)
34
+ OPCODE_CODES = OPCODES.values
35
+ MESSAGE_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
36
+ OPENING_OPCODES = OPCODES.values_at(:text, :binary)
34
37
 
35
38
  ERRORS = {
36
39
  :normal_closure => 1000,
@@ -50,13 +53,13 @@ module WebSocket
50
53
 
51
54
  def initialize(socket, options = {})
52
55
  super
53
- reset
54
56
 
57
+ @extensions = ::WebSocket::Extensions.new
55
58
  @reader = StreamReader.new
56
59
  @stage = 0
57
60
  @masking = options[:masking]
58
61
  @protocols = options[:protocols] || []
59
- @protocols = @protocols.strip.split(/\s*,\s*/) if String === @protocols
62
+ @protocols = @protocols.strip.split(/ *, */) if String === @protocols
60
63
  @require_masking = options[:require_masking]
61
64
  @ping_callbacks = {}
62
65
 
@@ -70,7 +73,7 @@ module WebSocket
70
73
  @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
71
74
 
72
75
  if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
73
- protos = protos.split(/\s*,\s*/) if String === protos
76
+ protos = protos.split(/ *, */) if String === protos
74
77
  @protocol = protos.find { |p| @protocols.include?(p) }
75
78
  @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
76
79
  end
@@ -80,35 +83,38 @@ module WebSocket
80
83
  "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
81
84
  end
82
85
 
86
+ def add_extension(extension)
87
+ @extensions.add(extension)
88
+ true
89
+ end
90
+
83
91
  def parse(data)
84
- data = data.bytes.to_a if data.respond_to?(:bytes)
85
92
  @reader.put(data)
86
93
  buffer = true
87
94
  while buffer
88
95
  case @stage
89
96
  when 0 then
90
97
  buffer = @reader.read(1)
91
- parse_opcode(buffer[0]) if buffer
98
+ parse_opcode(buffer.getbyte(0)) if buffer
92
99
 
93
100
  when 1 then
94
101
  buffer = @reader.read(1)
95
- parse_length(buffer[0]) if buffer
102
+ parse_length(buffer.getbyte(0)) if buffer
96
103
 
97
104
  when 2 then
98
- buffer = @reader.read(@length_size)
105
+ buffer = @reader.read(@frame.length_bytes)
99
106
  parse_extended_length(buffer) if buffer
100
107
 
101
108
  when 3 then
102
109
  buffer = @reader.read(4)
103
110
  if buffer
104
- @mask = buffer
111
+ @frame.masking_key = buffer
105
112
  @stage = 4
106
113
  end
107
114
 
108
115
  when 4 then
109
- buffer = @reader.read(@length)
116
+ buffer = @reader.read(@frame.length)
110
117
  if buffer
111
- @payload = buffer
112
118
  emit_frame(buffer)
113
119
  @stage = 0
114
120
  end
@@ -119,59 +125,6 @@ module WebSocket
119
125
  end
120
126
  end
121
127
 
122
- def frame(data, type = nil, code = nil)
123
- return queue([data, type, code]) if @ready_state <= 0
124
- return false unless @ready_state == 1
125
-
126
- data = data.to_s unless Array === data
127
- data = Driver.encode(data, :utf8) if String === data
128
-
129
- is_text = (String === data)
130
- opcode = OPCODES[type || (is_text ? :text : :binary)]
131
- buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
132
- insert = code ? 2 : 0
133
- length = buffer.size + insert
134
- header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
135
- offset = header + (@masking ? 4 : 0)
136
- masked = @masking ? MASK : 0
137
- frame = Array.new(offset)
138
-
139
- frame[0] = FIN | opcode
140
-
141
- if length <= 125
142
- frame[1] = masked | length
143
- elsif length <= 65535
144
- frame[1] = masked | 126
145
- frame[2] = (length >> 8) & BYTE
146
- frame[3] = length & BYTE
147
- else
148
- frame[1] = masked | 127
149
- frame[2] = (length >> 56) & BYTE
150
- frame[3] = (length >> 48) & BYTE
151
- frame[4] = (length >> 40) & BYTE
152
- frame[5] = (length >> 32) & BYTE
153
- frame[6] = (length >> 24) & BYTE
154
- frame[7] = (length >> 16) & BYTE
155
- frame[8] = (length >> 8) & BYTE
156
- frame[9] = length & BYTE
157
- end
158
-
159
- if code
160
- buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
161
- end
162
-
163
- if @masking
164
- mask = [rand(256), rand(256), rand(256), rand(256)]
165
- frame[header...offset] = mask
166
- buffer = Mask.mask(buffer, mask)
167
- end
168
-
169
- frame.concat(buffer)
170
-
171
- @socket.write(Driver.encode(frame, :binary))
172
- true
173
- end
174
-
175
128
  def text(message)
176
129
  frame(message, :text)
177
130
  end
@@ -202,9 +155,91 @@ module WebSocket
202
155
  end
203
156
  end
204
157
 
158
+ def frame(data, type = nil, code = nil)
159
+ return queue([data, type, code]) if @ready_state <= 0
160
+ return false unless @ready_state == 1
161
+
162
+ message = Message.new
163
+ frame = Frame.new
164
+ is_text = String === data
165
+
166
+ message.rsv1 = message.rsv2 = message.rsv3 = false
167
+ message.opcode = OPCODES[type || (is_text ? :text : :binary)]
168
+
169
+ payload = is_text ? data.bytes.to_a : data
170
+ if code
171
+ payload = [(code >> 8) & BYTE, code & BYTE, *payload]
172
+ end
173
+ message.data = payload.pack('C*')
174
+
175
+ if MESSAGE_OPCODES.include?(message.opcode)
176
+ message = @extensions.process_outgoing_message(message)
177
+ end
178
+
179
+ frame.final = true
180
+ frame.rsv1 = message.rsv1
181
+ frame.rsv2 = message.rsv2
182
+ frame.rsv3 = message.rsv3
183
+ frame.opcode = message.opcode
184
+ frame.masked = !!@masking
185
+ frame.masking_key = SecureRandom.random_bytes(4) if frame.masked
186
+ frame.length = message.data.bytesize
187
+ frame.payload = message.data
188
+
189
+ send_frame(frame)
190
+ true
191
+ end
192
+
205
193
  private
206
194
 
195
+ def send_frame(frame)
196
+ length = frame.length
197
+ header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
198
+ offset = header + (frame.masked ? 4 : 0)
199
+ buffer = []
200
+ masked = frame.masked ? MASK : 0
201
+
202
+ buffer[0] = (frame.final ? FIN : 0) |
203
+ (frame.rsv1 ? RSV1 : 0) |
204
+ (frame.rsv2 ? RSV2 : 0) |
205
+ (frame.rsv3 ? RSV3 : 0) |
206
+ frame.opcode
207
+
208
+ if length <= 125
209
+ buffer[1] = masked | length
210
+ elsif length <= 65535
211
+ buffer[1] = masked | 126
212
+ buffer[2] = (length >> 8) & BYTE
213
+ buffer[3] = length & BYTE
214
+ else
215
+ buffer[1] = masked | 127
216
+ buffer[2] = (length >> 56) & BYTE
217
+ buffer[3] = (length >> 48) & BYTE
218
+ buffer[4] = (length >> 40) & BYTE
219
+ buffer[5] = (length >> 32) & BYTE
220
+ buffer[6] = (length >> 24) & BYTE
221
+ buffer[7] = (length >> 16) & BYTE
222
+ buffer[8] = (length >> 8) & BYTE
223
+ buffer[9] = length & BYTE
224
+ end
225
+
226
+ if frame.masked
227
+ buffer.concat(frame.masking_key.bytes.to_a)
228
+ buffer.concat(Mask.mask(frame.payload, frame.masking_key).bytes.to_a)
229
+ else
230
+ buffer.concat(frame.payload.bytes.to_a)
231
+ end
232
+
233
+ @socket.write(buffer.pack('C*'))
234
+
235
+ rescue ::WebSocket::Extensions::ExtensionError => e
236
+ fail(:extension_error, e.message)
237
+ end
238
+
207
239
  def handshake_response
240
+ extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
241
+ @headers['Sec-WebSocket-Extensions'] = extensions if extensions
242
+
208
243
  start = 'HTTP/1.1 101 Switching Protocols'
209
244
  headers = [start, @headers.to_s, '']
210
245
  headers.join("\r\n")
@@ -212,9 +247,11 @@ module WebSocket
212
247
 
213
248
  def shutdown(code, reason)
214
249
  frame(reason, :close, code)
250
+ @frame = @message = nil
215
251
  @ready_state = 3
216
252
  @stage = 5
217
253
  emit(:close, CloseEvent.new(code, reason))
254
+ @extensions.close
218
255
  end
219
256
 
220
257
  def fail(type, message)
@@ -225,25 +262,30 @@ module WebSocket
225
262
  def parse_opcode(data)
226
263
  rsvs = [RSV1, RSV2, RSV3].map { |rsv| (data & rsv) == rsv }
227
264
 
228
- if rsvs.any?
265
+ @frame = Frame.new
266
+
267
+ @frame.final = (data & FIN) == FIN
268
+ @frame.rsv1 = rsvs[0]
269
+ @frame.rsv2 = rsvs[1]
270
+ @frame.rsv3 = rsvs[2]
271
+ @frame.opcode = (data & OPCODE)
272
+
273
+ unless @extensions.valid_frame_rsv?(@frame)
229
274
  return fail(:protocol_error,
230
- "One or more reserved bits are on: reserved1 = #{rsvs[0] ? 1 : 0}" +
231
- ", reserved2 = #{rsvs[1] ? 1 : 0 }" +
232
- ", reserved3 = #{rsvs[2] ? 1 : 0 }")
275
+ "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
276
+ ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
277
+ ", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
233
278
  end
234
279
 
235
- @final = (data & FIN) == FIN
236
- @opcode = (data & OPCODE)
237
-
238
- unless OPCODES.values.include?(@opcode)
239
- return fail(:protocol_error, "Unrecognized frame opcode: #{@opcode}")
280
+ unless OPCODES.values.include?(@frame.opcode)
281
+ return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
240
282
  end
241
283
 
242
- unless FRAGMENTED_OPCODES.include?(@opcode) or @final
243
- return fail(:protocol_error, "Received fragmented control frame: opcode = #{@opcode}")
284
+ unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
285
+ return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
244
286
  end
245
287
 
246
- if @mode and OPENING_OPCODES.include?(@opcode)
288
+ if @message and OPENING_OPCODES.include?(@frame.opcode)
247
289
  return fail(:protocol_error, 'Received new data frame but previous continuous frame is unfinished')
248
290
  end
249
291
 
@@ -251,36 +293,38 @@ module WebSocket
251
293
  end
252
294
 
253
295
  def parse_length(data)
254
- @masked = (data & MASK) == MASK
255
- if @require_masking and not @masked
296
+ @frame.masked = (data & MASK) == MASK
297
+ if @require_masking and not @frame.masked
256
298
  return fail(:unacceptable, 'Received unmasked frame but masking is required')
257
299
  end
258
300
 
259
- @length = (data & LENGTH)
301
+ @frame.length = (data & LENGTH)
260
302
 
261
- if @length >= 0 and @length <= 125
303
+ if @frame.length >= 0 and @frame.length <= 125
262
304
  return unless check_frame_length
263
- @stage = @masked ? 3 : 4
305
+ @stage = @frame.masked ? 3 : 4
264
306
  else
265
- @length_size = (@length == 126) ? 2 : 8
266
- @stage = 2
307
+ @frame.length_bytes = (@frame.length == 126) ? 2 : 8
308
+ @stage = 2
267
309
  end
268
310
  end
269
311
 
270
312
  def parse_extended_length(buffer)
271
- @length = integer(buffer)
313
+ @frame.length = integer(buffer)
272
314
 
273
- unless FRAGMENTED_OPCODES.include?(@opcode) or @length <= 125
274
- return fail(:protocol_error, "Received control frame having too long payload: #{@length}")
315
+ unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
316
+ return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
275
317
  end
276
318
 
277
319
  return unless check_frame_length
278
320
 
279
- @stage = @masked ? 3 : 4
321
+ @stage = @frame.masked ? 3 : 4
280
322
  end
281
323
 
282
324
  def check_frame_length
283
- if @buffer.size + @length > @max_length
325
+ length = @message ? @message.data.bytesize : 0
326
+
327
+ if length + @frame.length > @max_length
284
328
  fail(:too_large, 'WebSocket frame length too large')
285
329
  false
286
330
  else
@@ -289,64 +333,37 @@ module WebSocket
289
333
  end
290
334
 
291
335
  def emit_frame(buffer)
292
- payload = Mask.mask(buffer, @mask)
293
- is_final = @final
294
- opcode = @opcode
336
+ frame = @frame
337
+ opcode = frame.opcode
338
+ payload = frame.payload = Mask.mask(buffer, @frame.masking_key)
339
+ bytesize = payload.bytesize
340
+ bytes = payload.bytes.to_a
295
341
 
296
- @final = @opcode = @length = @length_size = @masked = @mask = nil
342
+ @frame = nil
297
343
 
298
344
  case opcode
299
345
  when OPCODES[:continuation] then
300
- return fail(:protocol_error, 'Received unexpected continuation frame') unless @mode
301
- @buffer.concat(payload)
302
- if is_final
303
- message = @buffer
304
- message = Driver.encode(message, :utf8) if @mode == :text
305
- reset
306
- if message
307
- emit(:message, MessageEvent.new(message))
308
- else
309
- fail(:encoding_error, 'Could not decode a text frame as UTF-8')
310
- end
311
- end
346
+ return fail(:protocol_error, 'Received unexpected continuation frame') unless @message
347
+ @message << frame
312
348
 
313
- when OPCODES[:text] then
314
- if is_final
315
- message = Driver.encode(payload, :utf8)
316
- if message
317
- emit(:message, MessageEvent.new(message))
318
- else
319
- fail(:encoding_error, 'Could not decode a text frame as UTF-8')
320
- end
321
- else
322
- @mode = :text
323
- @buffer.concat(payload)
324
- end
325
-
326
- when OPCODES[:binary] then
327
- if is_final
328
- emit(:message, MessageEvent.new(payload))
329
- else
330
- @mode = :binary
331
- @buffer.concat(payload)
332
- end
349
+ when OPCODES[:text], OPCODES[:binary] then
350
+ @message = Message.new
351
+ @message << frame
333
352
 
334
353
  when OPCODES[:close] then
335
- code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
354
+ code = (bytesize >= 2) ? 256 * bytes[0] + bytes[1] : nil
355
+ reason = (bytesize > 2) ? Driver.encode(bytes[2..-1] || [], :utf8) : nil
336
356
 
337
- unless (payload.size == 0) or
357
+ unless (bytesize == 0) or
338
358
  (code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or
339
359
  ERROR_CODES.include?(code)
340
360
  code = ERRORS[:protocol_error]
341
361
  end
342
362
 
343
- message = Driver.encode(payload[2..-1] || [], :utf8)
344
-
345
- if payload.size > 125 or message.nil?
363
+ if bytesize > 125 or (bytesize > 2 and reason.nil?)
346
364
  code = ERRORS[:protocol_error]
347
365
  end
348
366
 
349
- reason = (payload.size > 2) ? message : ''
350
367
  shutdown(code, reason || '')
351
368
 
352
369
  when OPCODES[:ping] then
@@ -358,17 +375,36 @@ module WebSocket
358
375
  @ping_callbacks.delete(message)
359
376
  callback.call if callback
360
377
  end
378
+
379
+ emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
361
380
  end
362
381
 
363
- def reset
364
- @buffer = []
365
- @mode = nil
382
+ def emit_message
383
+ message = @extensions.process_incoming_message(@message)
384
+ @message = nil
385
+
386
+ payload = message.data
387
+
388
+ case message.opcode
389
+ when OPCODES[:text] then
390
+ payload = Driver.encode(payload, :utf8)
391
+ when OPCODES[:binary]
392
+ payload = payload.bytes.to_a
393
+ end
394
+
395
+ if payload
396
+ emit(:message, MessageEvent.new(payload))
397
+ else
398
+ fail(:encoding_error, 'Could not decode a text frame as UTF-8')
399
+ end
400
+ rescue ::WebSocket::Extensions::ExtensionError => e
401
+ fail(:extension_error, e.message)
366
402
  end
367
403
 
368
- def integer(bytes)
404
+ def integer(buffer)
369
405
  number = 0
370
- bytes.each_with_index do |data, i|
371
- number += data << (8 * (bytes.size - 1 - i))
406
+ buffer.each_byte.with_index do |data, i|
407
+ number += data << (8 * (buffer.bytesize - 1 - i))
372
408
  end
373
409
  number
374
410
  end
@@ -0,0 +1,20 @@
1
+ module WebSocket
2
+ class Driver
3
+ class Hybi
4
+
5
+ class Frame
6
+ attr_accessor :final,
7
+ :rsv1,
8
+ :rsv2,
9
+ :rsv3,
10
+ :opcode,
11
+ :masked,
12
+ :masking_key,
13
+ :length_bytes,
14
+ :length,
15
+ :payload
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module WebSocket
2
+ class Driver
3
+ class Hybi
4
+
5
+ class Message
6
+ attr_accessor :rsv1,
7
+ :rsv2,
8
+ :rsv3,
9
+ :opcode,
10
+ :data
11
+
12
+ def initialize
13
+ @rsv1 = false
14
+ @rsv2 = false
15
+ @rsv3 = false
16
+ @opcode = nil
17
+ @data = Driver.encode('', :binary)
18
+ end
19
+
20
+ def <<(frame)
21
+ @rsv1 ||= frame.rsv1
22
+ @rsv2 ||= frame.rsv2
23
+ @rsv3 ||= frame.rsv3
24
+ @opcode ||= frame.opcode
25
+ @data << frame.payload
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -4,23 +4,22 @@ module WebSocket
4
4
  class Hybi
5
5
  class StreamReader
6
6
  def initialize
7
- @queue = []
7
+ @buffer = Driver.encode('', :binary)
8
8
  end
9
9
 
10
- def read(length)
11
- read_bytes(length)
10
+ def put(string)
11
+ return unless string and string.bytesize > 0
12
+ @buffer << Driver.encode(string, :binary)
12
13
  end
13
14
 
14
- def put(bytes)
15
- return unless bytes and bytes.size > 0
16
- @queue.concat(bytes)
17
- end
15
+ def read(length)
16
+ buffer_size = @buffer.bytesize
17
+ return nil if length > buffer_size
18
18
 
19
- private
19
+ chunk = @buffer.byteslice(0, length)
20
+ @buffer = @buffer.byteslice(length, buffer_size - length)
20
21
 
21
- def read_bytes(length)
22
- return nil if length > @queue.size
23
- @queue.shift(length)
22
+ chunk
24
23
  end
25
24
  end
26
25
  end
@@ -44,7 +44,7 @@ module WebSocket
44
44
  start = "CONNECT #{@origin.host}:#{port} HTTP/1.1"
45
45
  headers = [start, @headers.to_s, '']
46
46
 
47
- @socket.write(headers.join("\r\n"))
47
+ @socket.write(Driver.encode(headers.join("\r\n"), :binary))
48
48
  true
49
49
  end
50
50
 
@@ -22,7 +22,7 @@ module WebSocket
22
22
  url
23
23
  end
24
24
 
25
- %w[set_header start state frame text binary ping close].each do |method|
25
+ %w[add_extension set_header start frame text binary ping close].each do |method|
26
26
  define_method(method) do |*args, &block|
27
27
  if @delegate
28
28
  @delegate.__send__(method, *args, &block)
@@ -47,7 +47,8 @@ module WebSocket
47
47
  return unless @http.complete?
48
48
 
49
49
  @delegate = Driver.rack(self, @options)
50
- @delegate.on(:open) { open }
50
+ open
51
+
51
52
  EVENTS.each do |event|
52
53
  @delegate.on(event) { |e| emit(event, e) }
53
54
  end
@@ -90,7 +90,15 @@ module WebSocket
90
90
 
91
91
  def header_line(line)
92
92
  return false unless parsed = line.scan(HEADER_LINE).first
93
- @headers[HTTP.normalize_header(parsed[0])] = parsed[1].strip
93
+
94
+ key = HTTP.normalize_header(parsed[0])
95
+ value = parsed[1].strip
96
+
97
+ if @headers.has_key?(key)
98
+ @headers[key] << ', ' << value
99
+ else
100
+ @headers[key] = value
101
+ end
94
102
  true
95
103
  end
96
104
 
Binary file
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: java
6
6
  authors:
7
7
  - James Coglan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-08 00:00:00.000000000 Z
11
+ date: 2014-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: websocket-extensions
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - '>='
23
+ - !ruby/object:Gem::Version
24
+ version: 0.1.0
25
+ prerelease: false
26
+ type: :runtime
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: eventmachine
15
29
  version_requirements: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  version: '0'
25
39
  prerelease: false
26
40
  type: :development
41
+ - !ruby/object:Gem::Dependency
42
+ name: permessage_deflate
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ prerelease: false
54
+ type: :development
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake-compiler
29
57
  version_requirements: !ruby/object:Gem::Requirement
@@ -65,22 +93,24 @@ files:
65
93
  - ext/websocket-driver/WebsocketMaskService.java
66
94
  - ext/websocket-driver/extconf.rb
67
95
  - examples/tcp_server.rb
68
- - lib/websocket/websocket_mask.rb
69
96
  - lib/websocket/driver.rb
70
97
  - lib/websocket/http.rb
71
- - lib/websocket/http/request.rb
72
- - lib/websocket/http/response.rb
73
- - lib/websocket/http/headers.rb
74
- - lib/websocket/driver/hybi.rb
98
+ - lib/websocket/websocket_mask.rb
75
99
  - lib/websocket/driver/client.rb
100
+ - lib/websocket/driver/draft75.rb
76
101
  - lib/websocket/driver/draft76.rb
102
+ - lib/websocket/driver/event_emitter.rb
103
+ - lib/websocket/driver/headers.rb
104
+ - lib/websocket/driver/hybi.rb
77
105
  - lib/websocket/driver/proxy.rb
78
106
  - lib/websocket/driver/server.rb
79
- - lib/websocket/driver/draft75.rb
80
- - lib/websocket/driver/headers.rb
81
107
  - lib/websocket/driver/utf8_match.rb
82
- - lib/websocket/driver/event_emitter.rb
108
+ - lib/websocket/driver/hybi/frame.rb
109
+ - lib/websocket/driver/hybi/message.rb
83
110
  - lib/websocket/driver/hybi/stream_reader.rb
111
+ - lib/websocket/http/headers.rb
112
+ - lib/websocket/http/request.rb
113
+ - lib/websocket/http/response.rb
84
114
  - lib/websocket_mask.jar
85
115
  homepage: http://github.com/faye/websocket-driver-ruby
86
116
  licenses: