]>
git.proxmox.com Git - mirror_novnc.git/blob - core/websock.js
51d9b6250ebc5d3a7632b994bb45cf0c00edd058
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 with extra
9 * Websock has built-in receive queue buffering; the message event
10 * does not contain actual data but is simply a notification that
11 * there is new data available. Several rQ* methods are available to
12 * read binary data off of the receive queue.
16 * import Util from "./util";
19 /*jslint browser: true, bitwise: true */
22 /* [module] export default */ function Websock() {
25 this._websocket
= null; // WebSocket object
27 this._rQi
= 0; // Receive queue index
28 this._rQlen
= 0; // Next write position in the receive queue
29 this._rQbufferSize
= 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
30 this._rQmax
= this._rQbufferSize
/ 8;
31 // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
32 this._rQ
= null; // Receive queue
34 this._sQbufferSize
= 1024 * 10; // 10 KiB
35 // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
37 this._sQ
= null; // Send queue
39 this._eventHandlers
= {
40 'message': function () {},
41 'open': function () {},
42 'close': function () {},
43 'error': function () {}
49 // this has performance issues in some versions Chromium, and
50 // doesn't gain a tremendous amount of performance increase in Firefox
51 // at the moment. It may be valuable to turn it on in the future.
52 var ENABLE_COPYWITHIN
= false;
54 var MAX_RQ_GROW_SIZE
= 40 * 1024 * 1024; // 40 MiB
56 var typedArrayToString
= (function () {
57 // This is only for PhantomJS, which doesn't like apply-ing
60 var arr
= new Uint8Array([1, 2, 3]);
61 String
.fromCharCode
.apply(null, arr
);
62 return function (a
) { return String
.fromCharCode
.apply(null, a
); };
65 return String
.fromCharCode
.apply(
66 null, Array
.prototype.slice
.call(a
));
72 // Getters and Setters
81 get_rQi: function () {
85 set_rQi: function (val
) {
91 return this._rQlen
- this._rQi
;
94 rQpeek8: function () {
95 return this._rQ
[this._rQi
];
98 rQshift8: function () {
99 return this._rQ
[this._rQi
++];
102 rQskip8: function () {
106 rQskipBytes: function (num
) {
110 // TODO(directxman12): test performance with these vs a DataView
111 rQshift16: function () {
112 return (this._rQ
[this._rQi
++] << 8) +
113 this._rQ
[this._rQi
++];
116 rQshift32: function () {
117 return (this._rQ
[this._rQi
++] << 24) +
118 (this._rQ
[this._rQi
++] << 16) +
119 (this._rQ
[this._rQi
++] << 8) +
120 this._rQ
[this._rQi
++];
123 rQshiftStr: function (len
) {
124 if (typeof(len
) === 'undefined') { len
= this.rQlen(); }
125 var arr
= new Uint8Array(this._rQ
.buffer
, this._rQi
, len
);
127 return typedArrayToString(arr
);
130 rQshiftBytes: function (len
) {
131 if (typeof(len
) === 'undefined') { len
= this.rQlen(); }
133 return new Uint8Array(this._rQ
.buffer
, this._rQi
- len
, len
);
136 rQshiftTo: function (target
, len
) {
137 if (len
=== undefined) { len
= this.rQlen(); }
138 // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
139 target
.set(new Uint8Array(this._rQ
.buffer
, this._rQi
, len
));
143 rQwhole: function () {
144 return new Uint8Array(this._rQ
.buffer
, 0, this._rQlen
);
147 rQslice: function (start
, end
) {
149 return new Uint8Array(this._rQ
.buffer
, this._rQi
+ start
, end
- start
);
151 return new Uint8Array(this._rQ
.buffer
, this._rQi
+ start
, this._rQlen
- this._rQi
- start
);
155 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
156 // to be available in the receive queue. Return true if we need to
157 // wait (and possibly print a debug message), otherwise false.
158 rQwait: function (msg
, num
, goback
) {
159 var rQlen
= this._rQlen
- this._rQi
; // Skip rQlen() function call
162 if (this._rQi
< goback
) {
163 throw new Error("rQwait cannot backup " + goback
+ " bytes");
167 return true; // true means need more data
175 if (this._websocket
.bufferedAmount
!== 0) {
176 Util
.Debug("bufferedAmount: " + this._websocket
.bufferedAmount
);
179 if (this._sQlen
> 0 && this._websocket
.readyState
=== WebSocket
.OPEN
) {
180 this._websocket
.send(this._encode_message());
185 send: function (arr
) {
186 this._sQ
.set(arr
, this._sQlen
);
187 this._sQlen
+= arr
.length
;
191 send_string: function (str
) {
192 this.send(str
.split('').map(function (chr
) {
193 return chr
.charCodeAt(0);
198 off: function (evt
) {
199 this._eventHandlers
[evt
] = function () {};
202 on: function (evt
, handler
) {
203 this._eventHandlers
[evt
] = handler
;
206 _allocate_buffers: function () {
207 this._rQ
= new Uint8Array(this._rQbufferSize
);
208 this._sQ
= new Uint8Array(this._sQbufferSize
);
212 this._allocate_buffers();
214 this._websocket
= null;
217 open: function (uri
, protocols
) {
218 var ws_schema
= uri
.match(/^([a-z]+):\/\//)[1];
221 this._websocket
= new WebSocket(uri
, protocols
);
222 this._websocket
.binaryType
= 'arraybuffer';
224 this._websocket
.onmessage
= this._recv_message
.bind(this);
225 this._websocket
.onopen
= (function () {
226 Util
.Debug('>> WebSock.onopen');
227 if (this._websocket
.protocol
) {
228 Util
.Info("Server choose sub-protocol: " + this._websocket
.protocol
);
231 this._eventHandlers
.open();
232 Util
.Debug("<< WebSock.onopen");
234 this._websocket
.onclose
= (function (e
) {
235 Util
.Debug(">> WebSock.onclose");
236 this._eventHandlers
.close(e
);
237 Util
.Debug("<< WebSock.onclose");
239 this._websocket
.onerror
= (function (e
) {
240 Util
.Debug(">> WebSock.onerror: " + e
);
241 this._eventHandlers
.error(e
);
242 Util
.Debug("<< WebSock.onerror: " + e
);
247 if (this._websocket
) {
248 if ((this._websocket
.readyState
=== WebSocket
.OPEN
) ||
249 (this._websocket
.readyState
=== WebSocket
.CONNECTING
)) {
250 Util
.Info("Closing WebSocket connection");
251 this._websocket
.close();
254 this._websocket
.onmessage = function (e
) { return; };
259 _encode_message: function () {
260 // Put in a binary arraybuffer
261 // according to the spec, you can send ArrayBufferViews with the send method
262 return new Uint8Array(this._sQ
.buffer
, 0, this._sQlen
);
265 _expand_compact_rQ: function (min_fit
) {
266 var resizeNeeded
= min_fit
|| this._rQlen
- this._rQi
> this._rQbufferSize
/ 2;
269 // just double the size if we need to do compaction
270 this._rQbufferSize
*= 2;
272 // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
273 this._rQbufferSize
= (this._rQlen
- this._rQi
+ min_fit
) * 8;
277 // we don't want to grow unboundedly
278 if (this._rQbufferSize
> MAX_RQ_GROW_SIZE
) {
279 this._rQbufferSize
= MAX_RQ_GROW_SIZE
;
280 if (this._rQbufferSize
- this._rQlen
- this._rQi
< min_fit
) {
281 throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE
+ " bytes, and the new message could not fit");
286 var old_rQbuffer
= this._rQ
.buffer
;
287 this._rQmax
= this._rQbufferSize
/ 8;
288 this._rQ
= new Uint8Array(this._rQbufferSize
);
289 this._rQ
.set(new Uint8Array(old_rQbuffer
, this._rQi
));
291 if (ENABLE_COPYWITHIN
) {
292 this._rQ
.copyWithin(0, this._rQi
);
294 this._rQ
.set(new Uint8Array(this._rQ
.buffer
, this._rQi
));
298 this._rQlen
= this._rQlen
- this._rQi
;
302 _decode_message: function (data
) {
303 // push arraybuffer values onto the end
304 var u8
= new Uint8Array(data
);
305 if (u8
.length
> this._rQbufferSize
- this._rQlen
) {
306 this._expand_compact_rQ(u8
.length
);
308 this._rQ
.set(u8
, this._rQlen
);
309 this._rQlen
+= u8
.length
;
312 _recv_message: function (e
) {
314 this._decode_message(e
.data
);
315 if (this.rQlen() > 0) {
316 this._eventHandlers
.message();
317 // Compact the receive queue
318 if (this._rQlen
== this._rQi
) {
321 } else if (this._rQlen
> this._rQmax
) {
322 this._expand_compact_rQ();
325 Util
.Debug("Ignoring empty message");
328 var exception_str
= "";
330 exception_str
+= "\n name: " + exc
.name
+ "\n";
331 exception_str
+= " message: " + exc
.message
+ "\n";
334 if (typeof exc
.description
!== 'undefined') {
335 exception_str
+= " description: " + exc
.description
+ "\n";
338 if (typeof exc
.stack
!== 'undefined') {
339 exception_str
+= exc
.stack
;
342 if (exception_str
.length
> 0) {
343 Util
.Error("recv_message, caught exception: " + exception_str
);
345 Util
.Error("recv_message, caught exception: " + exc
);
348 if (typeof exc
.name
!== 'undefined') {
349 this._eventHandlers
.error(exc
.name
+ ": " + exc
.message
);
351 this._eventHandlers
.error(exc
);