]>
git.proxmox.com Git - mirror_novnc.git/blob - core/websock.js
2 * Websock: high-performance binary WebSockets
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
6 * Websock is similar to the standard WebSocket object but Websock
7 * enables communication with raw TCP sockets (i.e. the binary stream)
8 * via websockify. This is accomplished by base64 encoding the data
9 * stream between Websock and websockify.
11 * Websock has built-in receive queue buffering; the message event
12 * does not contain actual data but is simply a notification that
13 * there is new data available. Several rQ* methods are available to
14 * read binary data off of the receive queue.
18 * import Util from "./util";
19 * import Base64 from "./base64";
22 /*jslint browser: true, bitwise: true */
25 /* [module] export default */ function Websock() {
28 this._websocket
= null; // WebSocket object
30 this._rQi
= 0; // Receive queue index
31 this._rQlen
= 0; // Next write position in the receive queue
32 this._rQbufferSize
= 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
33 this._rQmax
= this._rQbufferSize
/ 8;
34 // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
35 this._rQ
= null; // Receive queue
37 this._sQbufferSize
= 1024 * 10; // 10 KiB
38 // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
40 this._sQ
= null; // Send queue
42 this._mode
= 'binary'; // Current WebSocket mode: 'binary', 'base64'
43 this.maxBufferedAmount
= 200;
45 this._eventHandlers
= {
46 'message': function () {},
47 'open': function () {},
48 'close': function () {},
49 'error': function () {}
55 // this has performance issues in some versions Chromium, and
56 // doesn't gain a tremendous amount of performance increase in Firefox
57 // at the moment. It may be valuable to turn it on in the future.
58 var ENABLE_COPYWITHIN
= false;
60 var MAX_RQ_GROW_SIZE
= 40 * 1024 * 1024; // 40 MiB
62 var typedArrayToString
= (function () {
63 // This is only for PhantomJS, which doesn't like apply-ing
66 var arr
= new Uint8Array([1, 2, 3]);
67 String
.fromCharCode
.apply(null, arr
);
68 return function (a
) { return String
.fromCharCode
.apply(null, a
); };
71 return String
.fromCharCode
.apply(
72 null, Array
.prototype.slice
.call(a
));
78 // Getters and Setters
87 get_rQi: function () {
91 set_rQi: function (val
) {
97 return this._rQlen
- this._rQi
;
100 rQpeek8: function () {
101 return this._rQ
[this._rQi
];
104 rQshift8: function () {
105 return this._rQ
[this._rQi
++];
108 rQskip8: function () {
112 rQskipBytes: function (num
) {
116 // TODO(directxman12): test performance with these vs a DataView
117 rQshift16: function () {
118 return (this._rQ
[this._rQi
++] << 8) +
119 this._rQ
[this._rQi
++];
122 rQshift32: function () {
123 return (this._rQ
[this._rQi
++] << 24) +
124 (this._rQ
[this._rQi
++] << 16) +
125 (this._rQ
[this._rQi
++] << 8) +
126 this._rQ
[this._rQi
++];
129 rQshiftStr: function (len
) {
130 if (typeof(len
) === 'undefined') { len
= this.rQlen(); }
131 var arr
= new Uint8Array(this._rQ
.buffer
, this._rQi
, len
);
133 return typedArrayToString(arr
);
136 rQshiftBytes: function (len
) {
137 if (typeof(len
) === 'undefined') { len
= this.rQlen(); }
139 return new Uint8Array(this._rQ
.buffer
, this._rQi
- len
, len
);
142 rQshiftTo: function (target
, len
) {
143 if (len
=== undefined) { len
= this.rQlen(); }
144 // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
145 target
.set(new Uint8Array(this._rQ
.buffer
, this._rQi
, len
));
149 rQwhole: function () {
150 return new Uint8Array(this._rQ
.buffer
, 0, this._rQlen
);
153 rQslice: function (start
, end
) {
155 return new Uint8Array(this._rQ
.buffer
, this._rQi
+ start
, end
- start
);
157 return new Uint8Array(this._rQ
.buffer
, this._rQi
+ start
, this._rQlen
- this._rQi
- start
);
161 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
162 // to be available in the receive queue. Return true if we need to
163 // wait (and possibly print a debug message), otherwise false.
164 rQwait: function (msg
, num
, goback
) {
165 var rQlen
= this._rQlen
- this._rQi
; // Skip rQlen() function call
168 if (this._rQi
< goback
) {
169 throw new Error("rQwait cannot backup " + goback
+ " bytes");
173 return true; // true means need more data
181 if (this._websocket
.bufferedAmount
!== 0) {
182 Util
.Debug("bufferedAmount: " + this._websocket
.bufferedAmount
);
185 if (this._websocket
.bufferedAmount
< this.maxBufferedAmount
) {
186 if (this._sQlen
> 0 && this._websocket
.readyState
=== WebSocket
.OPEN
) {
187 this._websocket
.send(this._encode_message());
193 Util
.Info("Delaying send, bufferedAmount: " +
194 this._websocket
.bufferedAmount
);
199 send: function (arr
) {
200 this._sQ
.set(arr
, this._sQlen
);
201 this._sQlen
+= arr
.length
;
205 send_string: function (str
) {
206 this.send(str
.split('').map(function (chr
) {
207 return chr
.charCodeAt(0);
212 off: function (evt
) {
213 this._eventHandlers
[evt
] = function () {};
216 on: function (evt
, handler
) {
217 this._eventHandlers
[evt
] = handler
;
220 _allocate_buffers: function () {
221 this._rQ
= new Uint8Array(this._rQbufferSize
);
222 this._sQ
= new Uint8Array(this._sQbufferSize
);
225 init: function (protocols
, ws_schema
) {
226 this._allocate_buffers();
228 this._websocket
= null;
230 // Check for full typed array support
232 if (('Uint8Array' in window
) &&
233 ('set' in Uint8Array
.prototype)) {
237 // Check for full binary type support in WebSockets
239 // https://github.com/Modernizr/Modernizr/issues/370
240 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
243 if (bt
&& ('binaryType' in WebSocket
.prototype ||
244 !!(new WebSocket(ws_schema
+ '://.').binaryType
))) {
245 Util
.Info("Detected binaryType support in WebSockets");
249 // Just ignore failed test localhost connection
252 // Default protocols if not specified
253 if (typeof(protocols
) === "undefined") {
254 protocols
= 'binary';
257 if (Array
.isArray(protocols
) && protocols
.indexOf('binary') > -1) {
258 protocols
= 'binary';
262 throw new Error("noVNC no longer supports base64 WebSockets. " +
263 "Please use a browser which supports binary WebSockets.");
266 if (protocols
!= 'binary') {
267 throw new Error("noVNC no longer supports base64 WebSockets. Please " +
268 "use the binary subprotocol instead.");
274 open: function (uri
, protocols
) {
275 var ws_schema
= uri
.match(/^([a-z]+):\/\//)[1];
276 protocols
= this.init(protocols
, ws_schema
);
278 this._websocket
= new WebSocket(uri
, protocols
);
280 if (protocols
.indexOf('binary') >= 0) {
281 this._websocket
.binaryType
= 'arraybuffer';
284 this._websocket
.onmessage
= this._recv_message
.bind(this);
285 this._websocket
.onopen
= (function () {
286 Util
.Debug('>> WebSock.onopen');
287 if (this._websocket
.protocol
) {
288 this._mode
= this._websocket
.protocol
;
289 Util
.Info("Server choose sub-protocol: " + this._websocket
.protocol
);
291 this._mode
= 'binary';
292 Util
.Error('Server select no sub-protocol!: ' + this._websocket
.protocol
);
295 if (this._mode
!= 'binary') {
296 throw new Error("noVNC no longer supports base64 WebSockets. Please " +
297 "use the binary subprotocol instead.");
301 this._eventHandlers
.open();
302 Util
.Debug("<< WebSock.onopen");
304 this._websocket
.onclose
= (function (e
) {
305 Util
.Debug(">> WebSock.onclose");
306 this._eventHandlers
.close(e
);
307 Util
.Debug("<< WebSock.onclose");
309 this._websocket
.onerror
= (function (e
) {
310 Util
.Debug(">> WebSock.onerror: " + e
);
311 this._eventHandlers
.error(e
);
312 Util
.Debug("<< WebSock.onerror: " + e
);
317 if (this._websocket
) {
318 if ((this._websocket
.readyState
=== WebSocket
.OPEN
) ||
319 (this._websocket
.readyState
=== WebSocket
.CONNECTING
)) {
320 Util
.Info("Closing WebSocket connection");
321 this._websocket
.close();
324 this._websocket
.onmessage = function (e
) { return; };
329 _encode_message: function () {
330 // Put in a binary arraybuffer
331 // according to the spec, you can send ArrayBufferViews with the send method
332 return new Uint8Array(this._sQ
.buffer
, 0, this._sQlen
);
335 _expand_compact_rQ: function (min_fit
) {
336 var resizeNeeded
= min_fit
|| this._rQlen
- this._rQi
> this._rQbufferSize
/ 2;
339 // just double the size if we need to do compaction
340 this._rQbufferSize
*= 2;
342 // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
343 this._rQbufferSize
= (this._rQlen
- this._rQi
+ min_fit
) * 8;
347 // we don't want to grow unboundedly
348 if (this._rQbufferSize
> MAX_RQ_GROW_SIZE
) {
349 this._rQbufferSize
= MAX_RQ_GROW_SIZE
;
350 if (this._rQbufferSize
- this._rQlen
- this._rQi
< min_fit
) {
351 throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE
+ " bytes, and the new message could not fit");
356 var old_rQbuffer
= this._rQ
.buffer
;
357 this._rQmax
= this._rQbufferSize
/ 8;
358 this._rQ
= new Uint8Array(this._rQbufferSize
);
359 this._rQ
.set(new Uint8Array(old_rQbuffer
, this._rQi
));
361 if (ENABLE_COPYWITHIN
) {
362 this._rQ
.copyWithin(0, this._rQi
);
364 this._rQ
.set(new Uint8Array(this._rQ
.buffer
, this._rQi
));
368 this._rQlen
= this._rQlen
- this._rQi
;
372 _decode_message: function (data
) {
373 // push arraybuffer values onto the end
374 var u8
= new Uint8Array(data
);
375 if (u8
.length
> this._rQbufferSize
- this._rQlen
) {
376 this._expand_compact_rQ(u8
.length
);
378 this._rQ
.set(u8
, this._rQlen
);
379 this._rQlen
+= u8
.length
;
382 _recv_message: function (e
) {
384 this._decode_message(e
.data
);
385 if (this.rQlen() > 0) {
386 this._eventHandlers
.message();
387 // Compact the receive queue
388 if (this._rQlen
== this._rQi
) {
391 } else if (this._rQlen
> this._rQmax
) {
392 this._expand_compact_rQ();
395 Util
.Debug("Ignoring empty message");
398 var exception_str
= "";
400 exception_str
+= "\n name: " + exc
.name
+ "\n";
401 exception_str
+= " message: " + exc
.message
+ "\n";
404 if (typeof exc
.description
!== 'undefined') {
405 exception_str
+= " description: " + exc
.description
+ "\n";
408 if (typeof exc
.stack
!== 'undefined') {
409 exception_str
+= exc
.stack
;
412 if (exception_str
.length
> 0) {
413 Util
.Error("recv_message, caught exception: " + exception_str
);
415 Util
.Error("recv_message, caught exception: " + exc
);
418 if (typeof exc
.name
!== 'undefined') {
419 this._eventHandlers
.error(exc
.name
+ ": " + exc
.message
);
421 this._eventHandlers
.error(exc
);