Files
bun.sh/test/js/web/websocket/websocket-server-echo.mjs
Kenta Iwasaki 20275aa040 fix(ws/client): handle short reads on payload frame length (#9027)
* fix(ws/client): handle short reads on payload frame length

In the WebSocket specification, control frames may not be fragmented.
However, the frame parser should handle fragmented control frames
nonetheless. Whether or not the frame parser is given a set of
fragmented bytes to parse is subject to the strategy in which the client
buffers received bytes.

All stages of the frame parser currently supports parsing frames
fragmented across multiple TCP segments except for the payload frame
length parsing stage.

This commit implements buffering the bytes of a frame's payload length
into a client instance so that the websocket client is able to properly
parse payload frame lengths despite there being a short read over
incoming TCP data.

A test is added to
test/js/web/websocket/websocket-client-short-read.test.ts which creates
a make-shift WebSocket server that performs short writes over a single
WebSocket frame. The test passes with this commit.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2024-02-21 14:31:57 -08:00

95 lines
2.3 KiB
JavaScript

import { createServer } from "node:http";
import { WebSocketServer } from "ws";
const server = createServer();
const wss = new WebSocketServer({
perMessageDeflate: false,
noServer: true,
});
server.on("listening", () => {
const { address, port, family } = server.address();
const { href } = new URL(family === "IPv6" ? `ws://[${address}]:${port}` : `ws://${address}:${port}`);
console.log(href);
console.error("Listening:", href);
});
server.on("request", (request, response) => {
console.error("Received request:", { ...request.headers });
response.end();
});
server.on("clientError", (error, socket) => {
console.error("Received client error:", error);
socket.end();
});
server.on("error", error => {
console.error("Received error:", error);
});
server.on("upgrade", (request, socket, head) => {
console.error("Received upgrade:", { ...request.headers });
socket.on("data", data => {
console.error("Received bytes:", data);
});
wss.handleUpgrade(request, socket, head, ws => {
wss.emit("connection", ws, request);
});
});
wss.on("connection", (ws, request) => {
console.error("Received connection:", request.socket.remoteAddress);
ws.on("message", message => {
console.error("Received message:", message);
ws.send(message);
if (message === "ping") {
console.error("Sending ping");
ws.ping();
} else if (message === "pong") {
console.error("Sending pong");
ws.pong();
} else if (message === "close") {
console.error("Sending close");
ws.close();
} else if (message === "terminate") {
console.error("Sending terminate");
ws.terminate();
}
});
ws.on("ping", data => {
console.error("Received ping:", data);
ws.ping(data);
});
ws.on("pong", data => {
console.error("Received pong:", data);
ws.pong(data);
});
ws.on("close", (code, reason) => {
console.error("Received close:", code, reason);
});
ws.on("error", error => {
console.error("Received error:", error);
});
});
server.on("close", () => {
console.error("Server closed");
});
process.on("exit", exitCode => {
console.error("Server exited:", exitCode);
});
const hostname = process.env.HOST || "127.0.0.1";
const port = parseInt(process.env.PORT || "0");
server.listen(port, hostname);