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 +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:
|