Skip to content

Commit

Permalink
perf: try to avoid buffer allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Jan 6, 2022
1 parent d2c935a commit 3bd436c
Showing 1 changed file with 54 additions and 29 deletions.
83 changes: 54 additions & 29 deletions lib/sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Sender {
/**
* Frames a piece of data according to the HyBi WebSocket protocol.
*
* @param {Buffer} data The data to frame
* @param {Buffer|String} data The data to frame
* @param {Object} options Options object
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
* FIN bit
Expand All @@ -58,7 +58,7 @@ class Sender {
* key
* @param {Number} options.opcode The opcode
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
* modified
* modified. Only relevant if data is a `Buffer`.
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
* RSV1 bit
* @return {Buffer[]} The framed data as a list of `Buffer` instances
Expand All @@ -80,12 +80,31 @@ class Sender {
}

skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
if (options.readOnly && !skipMasking) merge = true;

offset = 6;
}

let payloadLength = data.length;
let dataLength = options.payloadLength;

if (typeof data === 'string') {
if (!options.mask || skipMasking) {
if (dataLength == null) {
dataLength = Buffer.byteLength(data);
}
} else {
data = toBuffer(data);
dataLength = data.length;
}
} else if (Buffer.isBuffer(data)) {
dataLength = data.length;
merge = options.mask && options.readOnly && !skipMasking;
} else {
data = toBuffer(data);
dataLength = data.length;
merge = options.mask && toBuffer.readOnly && !skipMasking;
}

let payloadLength = dataLength;

if (data.length >= 65536) {
offset += 8;
Expand All @@ -95,18 +114,18 @@ class Sender {
payloadLength = 126;
}

const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);

target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
if (options.rsv1) target[0] |= 0x40;

target[1] = payloadLength;

if (payloadLength === 126) {
target.writeUInt16BE(data.length, 2);
target.writeUInt16BE(dataLength, 2);
} else if (payloadLength === 127) {
target[2] = target[3] = 0;
target.writeUIntBE(data.length, 4, 6);
target.writeUIntBE(dataLength, 4, 6);
}

if (!options.mask) return [target, data];
Expand Down Expand Up @@ -188,7 +207,7 @@ class Sender {
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: false
readOnly: true
}),
cb
);
Expand All @@ -203,16 +222,16 @@ class Sender {
* @public
*/
ping(data, mask, cb) {
const buf = toBuffer(data);
const payloadLength = Buffer.byteLength(data);

if (buf.length > 125) {
if (payloadLength > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
}

if (this._deflating) {
this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.doPing, data, mask, payloadLength, cb]);
} else {
this.doPing(buf, mask, toBuffer.readOnly, cb);
this.doPing(data, mask, payloadLength, cb);
}
}

Expand All @@ -221,11 +240,10 @@ class Sender {
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @private
*/
doPing(data, mask, readOnly, cb) {
doPing(data, mask, payloadLength, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
Expand All @@ -234,7 +252,8 @@ class Sender {
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly
payloadLength,
readOnly: true
}),
cb
);
Expand All @@ -249,16 +268,16 @@ class Sender {
* @public
*/
pong(data, mask, cb) {
const buf = toBuffer(data);
const payloadLength = Buffer.byteLength(data);

if (buf.length > 125) {
if (payloadLength > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
}

if (this._deflating) {
this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.doPong, data, mask, payloadLength, cb]);
} else {
this.doPong(buf, mask, toBuffer.readOnly, cb);
this.doPong(data, mask, payloadLength, cb);
}
}

Expand All @@ -267,11 +286,10 @@ class Sender {
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @private
*/
doPong(data, mask, readOnly, cb) {
doPong(data, mask, payloadLength, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
Expand All @@ -280,7 +298,8 @@ class Sender {
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly
payloadLength,
readOnly: true
}),
cb
);
Expand All @@ -303,11 +322,12 @@ class Sender {
* @public
*/
send(data, options, cb) {
const buf = toBuffer(data);
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
let opcode = options.binary ? 2 : 1;
let rsv1 = options.compress;

let payloadLength = options.payloadLength;

if (this._firstFragment) {
this._firstFragment = false;
if (
Expand All @@ -319,7 +339,10 @@ class Sender {
: 'client_no_context_takeover'
]
) {
rsv1 = buf.length >= perMessageDeflate._threshold;
if (payloadLength == null) {
payloadLength = Buffer.byteLength(data);
}
rsv1 = payloadLength >= perMessageDeflate._threshold;
}
this._compress = rsv1;
} else {
Expand All @@ -337,24 +360,26 @@ class Sender {
mask: options.mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: toBuffer.readOnly
payloadLength,
readOnly: true
};

if (this._deflating) {
this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
this.enqueue([this.dispatch, data, this._compress, opts, cb]);
} else {
this.dispatch(buf, this._compress, opts, cb);
this.dispatch(data, this._compress, opts, cb);
}
} else {
this.sendFrame(
Sender.frame(buf, {
Sender.frame(data, {
fin: options.fin,
rsv1: false,
opcode,
mask: options.mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: toBuffer.readOnly
payloadLength,
readOnly: true
}),
cb
);
Expand Down

0 comments on commit 3bd436c

Please sign in to comment.