websocket-driver 0.4.0-java → 0.5.0-java

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