Skip to content

Commit 46fdc2f

Browse files
feat: slice write buffer according to the maxPayload value
The server will now include a "maxPayload" field in the handshake details, allowing the clients to decide how many packets they have to send to stay under the maxHttpBufferSize value. Related: - socketio/socket.io-client#1531 - socketio/engine.io@088dcb4
1 parent f4725f1 commit 46fdc2f

File tree

11 files changed

+13694
-261
lines changed

11 files changed

+13694
-261
lines changed

lib/socket.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { transports } from "./transports/index.js";
2-
import { installTimerFunctions } from "./util.js";
2+
import { installTimerFunctions, byteLength } from "./util.js";
33
import parseqs from "parseqs";
44
import parseuri from "parseuri";
55
import debugModule from "debug"; // debug()
@@ -249,6 +249,7 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
249249
private clearTimeoutFn: typeof clearTimeout;
250250
private offlineEventListener;
251251
private upgrading: boolean;
252+
private maxPayload?: number;
252253

253254
private readonly opts: Partial<SocketOptions>;
254255
private readonly secure: boolean;
@@ -676,6 +677,7 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
676677
this.upgrades = this.filterUpgrades(data.upgrades);
677678
this.pingInterval = data.pingInterval;
678679
this.pingTimeout = data.pingTimeout;
680+
this.maxPayload = data.maxPayload;
679681
this.onOpen();
680682
// In case open handler closes socket
681683
if ("closed" === this.readyState) return;
@@ -729,15 +731,46 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
729731
!this.upgrading &&
730732
this.writeBuffer.length
731733
) {
732-
debug("flushing %d packets in socket", this.writeBuffer.length);
733-
this.transport.send(this.writeBuffer);
734+
const packets = this.getWritablePackets();
735+
debug("flushing %d packets in socket", packets.length);
736+
this.transport.send(packets);
734737
// keep track of current length of writeBuffer
735738
// splice writeBuffer and callbackBuffer on `drain`
736-
this.prevBufferLen = this.writeBuffer.length;
739+
this.prevBufferLen = packets.length;
737740
this.emitReserved("flush");
738741
}
739742
}
740743

744+
/**
745+
* Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP
746+
* long-polling)
747+
*
748+
* @private
749+
*/
750+
private getWritablePackets() {
751+
const shouldCheckPayloadSize =
752+
this.maxPayload &&
753+
this.transport.name === "polling" &&
754+
this.writeBuffer.length > 1;
755+
if (!shouldCheckPayloadSize) {
756+
return this.writeBuffer;
757+
}
758+
let payloadSize = 1; // first packet type
759+
for (let i = 0; i < this.writeBuffer.length; i++) {
760+
const data = this.writeBuffer[i].data;
761+
if (data) {
762+
payloadSize += byteLength(data);
763+
}
764+
if (i > 0 && payloadSize > this.maxPayload) {
765+
debug("only send %d out of %d packets", i, this.writeBuffer.length);
766+
return this.writeBuffer.slice(0, i);
767+
}
768+
payloadSize += 2; // separator + packet type
769+
}
770+
debug("payload size is %d (max: %d)", payloadSize, this.maxPayload);
771+
return this.writeBuffer;
772+
}
773+
741774
/**
742775
* Sends a message.
743776
*

lib/transports/websocket.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export class WS extends Transport {
141141

142142
if (this.opts.perMessageDeflate) {
143143
const len =
144+
// @ts-ignore
144145
"string" === typeof data ? Buffer.byteLength(data) : data.length;
145146
if (len < this.opts.perMessageDeflate.threshold) {
146147
opts.compress = false;

lib/util.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,34 @@ export function installTimerFunctions(obj, opts) {
2222
obj.clearTimeoutFn = clearTimeout.bind(globalThis);
2323
}
2424
}
25+
26+
// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
27+
const BASE64_OVERHEAD = 1.33;
28+
29+
// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9
30+
export function byteLength(obj) {
31+
if (typeof obj === "string") {
32+
return utf8Length(obj);
33+
}
34+
// arraybuffer or blob
35+
return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);
36+
}
37+
38+
function utf8Length(str) {
39+
let c = 0,
40+
length = 0;
41+
for (let i = 0, l = str.length; i < l; i++) {
42+
c = str.charCodeAt(i);
43+
if (c < 0x80) {
44+
length += 1;
45+
} else if (c < 0x800) {
46+
length += 2;
47+
} else if (c < 0xd800 || c >= 0xe000) {
48+
length += 3;
49+
} else {
50+
i++;
51+
length += 4;
52+
}
53+
}
54+
return length;
55+
}

0 commit comments

Comments
 (0)