Skip to content

Commit fc21c4a

Browse files
fix: fix websocket and webtransport send callbacks (#699)
With the `websocket` transport, the callbacks which indicate that the packets are actually written were not properly called. Example: ```js socket.send("hello", () => { // the message has been written to the underlying transport }); ``` The bug was caused by the `websocket` transport (and `webtransport` as well) having its `supportsFraming` property set to `true`, despite having been changed in [1] to emit a single `drain` event for each batch of messages written to the transport like the `polling` transport always did. Note that although [1] is partially reverted in [2], the new `drain` event behavior is preserved as called out in that commit's message. The `supportsFraming` attribute was introduced in [3] (amended by [4]) as a way to distinguish transports that emit one `drain` per message from those that emit one `drain` per batch. Since the delivery of `send` callbacks depends on matching `drain` events with `transport.send` calls, that distinction is vital to correct behavior. However, now that all transports have converged to "one `drain` per batch" behavior, this `supportsFraming` property can be retired (and the code for calling callbacks simplified). [1]: #618 [2]: a65a047 [3]: #130 [4]: #132 Related: #698
1 parent 79ea52d commit fc21c4a

File tree

9 files changed

+41
-61
lines changed

9 files changed

+41
-61
lines changed

lib/socket.ts

+14-24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface SendOptions {
1414

1515
type ReadyState = "opening" | "open" | "closing" | "closed";
1616

17+
type SendCallback = (transport: Transport) => void;
18+
1719
export class Socket extends EventEmitter {
1820
public readonly protocol: number;
1921
// TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory
@@ -27,8 +29,8 @@ export class Socket extends EventEmitter {
2729
private upgrading = false;
2830
private upgraded = false;
2931
private writeBuffer: Packet[] = [];
30-
private packetsFn: Array<() => void> = [];
31-
private sentCallbackFn: any[] = [];
32+
private packetsFn: SendCallback[] = [];
33+
private sentCallbackFn: SendCallback[][] = [];
3234
private cleanupFn: any[] = [];
3335
private pingTimeoutTimer;
3436
private pingIntervalTimer;
@@ -395,19 +397,11 @@ export class Socket extends EventEmitter {
395397
// the message was sent successfully, execute the callback
396398
const onDrain = () => {
397399
if (this.sentCallbackFn.length > 0) {
398-
const seqFn = this.sentCallbackFn.splice(0, 1)[0];
399-
if ("function" === typeof seqFn) {
400-
debug("executing send callback");
401-
seqFn(this.transport);
402-
} else if (Array.isArray(seqFn)) {
403-
debug("executing batch send callback");
404-
const l = seqFn.length;
405-
let i = 0;
406-
for (; i < l; i++) {
407-
if ("function" === typeof seqFn[i]) {
408-
seqFn[i](this.transport);
409-
}
410-
}
400+
debug("executing batch send callback");
401+
const seqFn = this.sentCallbackFn.shift();
402+
const l = seqFn.length;
403+
for (let i = 0; i < l; i++) {
404+
seqFn[i](this.transport);
411405
}
412406
}
413407
};
@@ -428,7 +422,7 @@ export class Socket extends EventEmitter {
428422
* @return {Socket} for chaining
429423
* @api public
430424
*/
431-
public send(data: RawData, options?: SendOptions, callback?: () => void) {
425+
public send(data: RawData, options?: SendOptions, callback?: SendCallback) {
432426
this.sendPacket("message", data, options, callback);
433427
return this;
434428
}
@@ -440,7 +434,7 @@ export class Socket extends EventEmitter {
440434
* @param options
441435
* @param callback
442436
*/
443-
public write(data: RawData, options?: SendOptions, callback?: () => void) {
437+
public write(data: RawData, options?: SendOptions, callback?: SendCallback) {
444438
this.sendPacket("message", data, options, callback);
445439
return this;
446440
}
@@ -459,7 +453,7 @@ export class Socket extends EventEmitter {
459453
type: PacketType,
460454
data?: RawData,
461455
options: SendOptions = {},
462-
callback?: () => void
456+
callback?: SendCallback
463457
) {
464458
if ("function" === typeof options) {
465459
callback = options;
@@ -485,7 +479,7 @@ export class Socket extends EventEmitter {
485479
this.writeBuffer.push(packet);
486480

487481
// add send callback to object, if defined
488-
if (callback) this.packetsFn.push(callback);
482+
if ("function" === typeof callback) this.packetsFn.push(callback);
489483

490484
this.flush();
491485
}
@@ -507,11 +501,7 @@ export class Socket extends EventEmitter {
507501
this.server.emit("flush", this, this.writeBuffer);
508502
const wbuf = this.writeBuffer;
509503
this.writeBuffer = [];
510-
if (!this.transport.supportsFraming) {
511-
this.sentCallbackFn.push(this.packetsFn);
512-
} else {
513-
this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn);
514-
}
504+
this.sentCallbackFn.push(this.packetsFn);
515505
this.packetsFn = [];
516506
this.transport.send(wbuf);
517507
this.emit("drain");

lib/transport.ts

-5
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,6 @@ export abstract class Transport extends EventEmitter {
137137
this.emit("close");
138138
}
139139

140-
/**
141-
* Advertise framing support.
142-
*/
143-
abstract get supportsFraming();
144-
145140
/**
146141
* The name of the transport.
147142
*/

lib/transports-uws/polling.ts

-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ export class Polling extends Transport {
4242
return "polling";
4343
}
4444

45-
get supportsFraming() {
46-
return false;
47-
}
48-
4945
/**
5046
* Overrides onRequest.
5147
*

lib/transports-uws/websocket.ts

-9
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ export class WebSocket extends Transport {
3737
return true;
3838
}
3939

40-
/**
41-
* Advertise framing support.
42-
*
43-
* @api public
44-
*/
45-
get supportsFraming() {
46-
return true;
47-
}
48-
4940
/**
5041
* Writes a packet payload.
5142
*

lib/transports/polling.ts

-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ export class Polling extends Transport {
4242
return "polling";
4343
}
4444

45-
get supportsFraming() {
46-
return false;
47-
}
48-
4945
/**
5046
* Overrides onRequest.
5147
*

lib/transports/websocket.ts

-9
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,6 @@ export class WebSocket extends Transport {
4545
return true;
4646
}
4747

48-
/**
49-
* Advertise framing support.
50-
*
51-
* @api public
52-
*/
53-
get supportsFraming() {
54-
return true;
55-
}
56-
5748
/**
5849
* Writes a packet payload.
5950
*

lib/transports/webtransport.ts

-4
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ export class WebTransport extends Transport {
4444
return "webtransport";
4545
}
4646

47-
get supportsFraming() {
48-
return true;
49-
}
50-
5147
async send(packets) {
5248
this.writable = false;
5349

test/server.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -2759,13 +2759,23 @@ describe("server", () => {
27592759
});
27602760
});
27612761

2762-
it("should execute in multipart packet", (done) => {
2762+
it("should execute in multipart packet (websocket)", (done) => {
27632763
const engine = listen((port) => {
2764-
const socket = new ClientSocket(`ws://localhost:${port}`);
2764+
const socket = new ClientSocket(`ws://localhost:${port}`, {
2765+
transports: ["websocket"],
2766+
});
27652767
let i = 0;
27662768
let j = 0;
27672769

27682770
engine.on("connection", (conn) => {
2771+
conn.send("d", (transport) => {
2772+
i++;
2773+
});
2774+
2775+
conn.send("c", (transport) => {
2776+
i++;
2777+
});
2778+
27692779
conn.send("b", (transport) => {
27702780
i++;
27712781
});

test/webtransport.mjs

+15
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,21 @@ describe("WebTransport", () => {
373373
});
374374
});
375375

376+
it("should invoke send callbacks (server to client)", (done) => {
377+
setup({}, async ({ engine, h3Server, socket, reader }) => {
378+
const messageCount = 4;
379+
let receivedCallbacks = 0;
380+
381+
for (let i = 0; i < messageCount; i++) {
382+
socket.send("hello", () => {
383+
if (++receivedCallbacks === messageCount) {
384+
success(engine, h3Server, done);
385+
}
386+
});
387+
}
388+
});
389+
});
390+
376391
it("should send some binary data (client to server)", (done) => {
377392
setup({}, async ({ engine, h3Server, socket, writer }) => {
378393
socket.on("data", (data) => {

0 commit comments

Comments
 (0)