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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +10 -0
- data/examples/tcp_server.rb +2 -0
- data/ext/websocket-driver/WebsocketMaskService.java +8 -18
- data/ext/websocket-driver/websocket_mask.c +17 -13
- data/lib/websocket/driver.rb +16 -20
- data/lib/websocket/driver/client.rb +14 -3
- data/lib/websocket/driver/draft75.rb +1 -2
- data/lib/websocket/driver/draft76.rb +6 -10
- data/lib/websocket/driver/hybi.rb +174 -138
- data/lib/websocket/driver/hybi/frame.rb +20 -0
- data/lib/websocket/driver/hybi/message.rb +31 -0
- data/lib/websocket/driver/hybi/stream_reader.rb +10 -11
- data/lib/websocket/driver/proxy.rb +1 -1
- data/lib/websocket/driver/server.rb +3 -2
- data/lib/websocket/http/headers.rb +9 -1
- data/lib/websocket_mask.jar +0 -0
- metadata +40 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc15766e2fa0e536038e29fadff1132446bce140
|
4
|
+
data.tar.gz: ad665aaf5f8088712fd10524cb99ab8911013215
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f11999301bb7c87b560543d2668841fe03ddcfd2545f49009096c707a5d266ffedbc3430450579a36218b4d09c8925923cf27607d45a215d2cfb71832473bb96
|
7
|
+
data.tar.gz: 9c79cd87ff2541ee12d0b953f9993dae2db27460a39bb197a4115f68f727479794ecd943d5ab7fb57293d2fac7ddf59ceda291355e42b5fdb80be8ec93f48a54
|
data/CHANGELOG.md
CHANGED
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
|
data/examples/tcp_server.rb
CHANGED
@@ -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()
|
43
|
-
return payload;
|
44
|
-
}
|
41
|
+
if (mask.isNil()) return payload;
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
byte[] payload_a = ((RubyString)payload).getBytes();
|
44
|
+
byte[] mask_a = ((RubyString)mask).getBytes();
|
45
|
+
int i, n = payload_a.length;
|
49
46
|
|
50
|
-
|
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
|
-
|
59
|
-
m = maskArray[i % 4];
|
60
|
-
unmasked.set(i, p ^ m);
|
50
|
+
payload_a[i] ^= mask_a[i % 4];
|
61
51
|
}
|
62
|
-
return
|
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
|
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
|
16
|
-
|
17
|
-
|
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 ||
|
26
|
+
if (mask == Qnil || RSTRING_LEN(mask) != 4) {
|
21
27
|
return payload;
|
22
28
|
}
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
payload_s = RSTRING_PTR(payload);
|
31
|
+
mask_s = RSTRING_PTR(mask);
|
32
|
+
n = RSTRING_LEN(payload);
|
26
33
|
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
}
|
data/lib/websocket/driver.rb
CHANGED
@@ -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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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(
|
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
|
62
|
-
|
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
|
-
|
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
|
|
@@ -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
|
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.
|
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.
|
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
|
32
|
-
|
33
|
-
OPENING_OPCODES
|
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(
|
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(
|
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
|
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
|
102
|
+
parse_length(buffer.getbyte(0)) if buffer
|
96
103
|
|
97
104
|
when 2 then
|
98
|
-
buffer = @reader.read(@
|
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
|
-
@
|
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
|
-
|
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 = #{
|
231
|
-
", reserved2 = #{
|
232
|
-
", reserved3 = #{
|
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
|
-
|
236
|
-
|
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
|
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 @
|
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
|
-
@
|
266
|
-
@stage
|
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
|
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
|
-
|
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
|
-
|
293
|
-
|
294
|
-
|
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
|
-
@
|
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 @
|
301
|
-
@
|
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
|
-
|
315
|
-
|
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
|
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 (
|
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
|
-
|
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
|
364
|
-
|
365
|
-
@
|
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(
|
404
|
+
def integer(buffer)
|
369
405
|
number = 0
|
370
|
-
|
371
|
-
number += data << (8 * (
|
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,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
|
-
@
|
7
|
+
@buffer = Driver.encode('', :binary)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
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
|
15
|
-
|
16
|
-
|
17
|
-
end
|
15
|
+
def read(length)
|
16
|
+
buffer_size = @buffer.bytesize
|
17
|
+
return nil if length > buffer_size
|
18
18
|
|
19
|
-
|
19
|
+
chunk = @buffer.byteslice(0, length)
|
20
|
+
@buffer = @buffer.byteslice(length, buffer_size - length)
|
20
21
|
|
21
|
-
|
22
|
-
return nil if length > @queue.size
|
23
|
-
@queue.shift(length)
|
22
|
+
chunk
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
@@ -22,7 +22,7 @@ module WebSocket
|
|
22
22
|
url
|
23
23
|
end
|
24
24
|
|
25
|
-
%w[set_header start
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/websocket_mask.jar
CHANGED
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
|
+
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
|
+
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/
|
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/
|
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:
|