commit 23935e4b50a957ebd2de9f2b57a60b1ad63ded54
parent 5007737be262f4862e79c097810a915409e79fe4
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date: Tue, 25 Nov 2025 10:26:22 +0000
Bug 2001617 - Add plaintext WS server r=necko-reviewers,kershaw
Differential Revision: https://phabricator.services.mozilla.com/D273787
Diffstat:
2 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/netwerk/test/httpserver/NodeServer.sys.mjs b/netwerk/test/httpserver/NodeServer.sys.mjs
@@ -875,6 +875,52 @@ export class NodeWebSocketServer extends BaseNodeServer {
}
}
+// unencrypted websocket server
+
+class NodeWebSocketPlainServerCode extends BaseNodeHTTPServerCode {
+ static async startServer(port) {
+ const http = require("http");
+ global.server = http.createServer(BaseNodeHTTPServerCode.globalHandler);
+
+ let node_ws_root = `${__dirname}/../node-ws`;
+ const WS = require(`${node_ws_root}/lib/websocket`);
+ WS.Server = require(`${node_ws_root}/lib/websocket-server`);
+ global.webSocketServer = new WS.Server({ server: global.server });
+ global.webSocketServer.on("connection", function connection(ws) {
+ ws.on("message", data =>
+ NodeWebSocketServerCode.messageHandler(data, ws)
+ );
+ });
+
+ let serverPort = await ADB.listenAndForwardPort(global.server, port);
+ return serverPort;
+ }
+}
+
+export class NodeWebSocketPlainServer extends BaseNodeServer {
+ _protocol = "ws";
+ /// Starts the server
+ /// @port - default 0
+ /// when provided, will attempt to listen on that port.
+ async start(port = 0) {
+ this.processId = await NodeServer.fork();
+
+ await this.execute(BaseNodeHTTPServerCode);
+ await this.execute(NodeWebSocketServerCode);
+ await this.execute(NodeWebSocketPlainServerCode);
+ await this.execute(ADB);
+ this._port = await this.execute(
+ `NodeWebSocketPlainServerCode.startServer(${port})`
+ );
+ await this.execute(`global.path_handlers = {};`);
+ await this.execute(`global.wsInputHandler = null;`);
+ }
+
+ async registerMessageHandler(handler) {
+ return this.execute(`global.wsInputHandler = ${handler.toString()}`);
+ }
+}
+
// websocket http2 server
// This code is inspired by
// https://github.com/szmarczak/http2-wrapper/blob/master/examples/ws/server.js
@@ -1029,10 +1075,11 @@ export class WebSocketConnection {
}
}
- static makeWebSocketChan() {
- let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
- Ci.nsIWebSocketChannel
- );
+ static makeWebSocketChan(url) {
+ let protocol = url.startsWith("wss:") ? "wss" : "ws";
+ let chan = Cc[
+ `@mozilla.org/network/protocol;1?name=${protocol}`
+ ].createInstance(Ci.nsIWebSocketChannel);
chan.initLoadInfo(
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
@@ -1044,7 +1091,7 @@ export class WebSocketConnection {
}
// Returns a promise that resolves when the websocket channel is opened.
open(url) {
- this._ws = WebSocketConnection.makeWebSocketChan();
+ this._ws = WebSocketConnection.makeWebSocketChan(url);
let uri = Services.io.newURI(url);
this._ws.asyncOpen(uri, url, {}, 0, this, null);
return this._openPromise;
diff --git a/netwerk/test/unit/test_websocket_server.js b/netwerk/test/unit/test_websocket_server.js
@@ -9,6 +9,7 @@
/* import-globals-from head_channels.js */
const {
+ NodeWebSocketPlainServer,
NodeWebSocketServer,
NodeWebSocketHttp2Server,
NodeHTTPProxyServer,
@@ -66,6 +67,41 @@ async function channelOpenPromise(url, msg) {
}
// h1.1 direct
+async function test_h1_plain_websocket_direct() {
+ let wss = new NodeWebSocketPlainServer();
+ await wss.start();
+ registerCleanupFunction(async () => wss.stop());
+ Assert.notEqual(wss.port(), null);
+ await wss.registerMessageHandler((data, ws) => {
+ ws.send(data);
+ });
+ let url = `ws://localhost:${wss.port()}`;
+ const msg = "test websocket";
+
+ let conn = new WebSocketConnection();
+ await conn.open(url);
+ conn.send(msg);
+ let mess1 = await conn.receiveMessages();
+ Assert.deepEqual(mess1, [msg]);
+
+ // Now send 3 more, and check that we received all of them
+ conn.send(msg);
+ conn.send(msg);
+ conn.send(msg);
+ let mess2 = [];
+ while (mess2.length < 3) {
+ // receive could return 1, 2 or all 3 replies.
+ mess2 = mess2.concat(await conn.receiveMessages());
+ }
+ Assert.deepEqual(mess2, [msg, msg, msg]);
+
+ conn.close();
+ let { status } = await conn.finished();
+
+ Assert.equal(status, Cr.NS_OK);
+}
+
+// h1.1 direct
async function test_h1_websocket_direct() {
let wss = new NodeWebSocketServer();
await wss.start();
@@ -416,6 +452,7 @@ async function test_websocket_fallback() {
checkConnectionActivities(observer.activites, "localhost", wss.port());
}
+add_task(test_h1_plain_websocket_direct);
add_task(test_h1_websocket_direct);
add_task(test_h2_websocket_direct);
add_task(test_h1_ws_with_secure_h1_proxy);