feat(test): add end-to-end WebSocket proxy test coverage
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-04 - 27.3.0 - feat(test)
|
||||||
|
add end-to-end WebSocket proxy test coverage
|
||||||
|
|
||||||
|
- add comprehensive WebSocket e2e tests for upgrade handling, bidirectional messaging, header forwarding, close propagation, and large payloads
|
||||||
|
- add ws and @types/ws as development dependencies to support the new test suite
|
||||||
|
|
||||||
## 2026-04-04 - 27.2.0 - feat(metrics)
|
## 2026-04-04 - 27.2.0 - feat(metrics)
|
||||||
add frontend and backend protocol distribution metrics
|
add frontend and backend protocol distribution metrics
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,10 @@
|
|||||||
"@git.zone/tstest": "^3.6.0",
|
"@git.zone/tstest": "^3.6.0",
|
||||||
"@push.rocks/smartserve": "^2.0.3",
|
"@push.rocks/smartserve": "^2.0.3",
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.2",
|
||||||
"why-is-node-running": "^3.2.2"
|
"why-is-node-running": "^3.2.2",
|
||||||
|
"ws": "^8.20.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartcrypto": "^2.0.4",
|
"@push.rocks/smartcrypto": "^2.0.4",
|
||||||
|
|||||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -45,12 +45,18 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.5.0
|
specifier: ^25.5.0
|
||||||
version: 25.5.0
|
version: 25.5.0
|
||||||
|
'@types/ws':
|
||||||
|
specifier: ^8.18.1
|
||||||
|
version: 8.18.1
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2
|
version: 6.0.2
|
||||||
why-is-node-running:
|
why-is-node-running:
|
||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2
|
version: 3.2.2
|
||||||
|
ws:
|
||||||
|
specifier: ^8.20.0
|
||||||
|
version: 8.20.0
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -3304,18 +3310,6 @@ packages:
|
|||||||
wrappy@1.0.2:
|
wrappy@1.0.2:
|
||||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||||
|
|
||||||
ws@8.19.0:
|
|
||||||
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
ws@8.20.0:
|
ws@8.20.0:
|
||||||
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@@ -5296,7 +5290,7 @@ snapshots:
|
|||||||
'@push.rocks/smartenv': 6.0.0
|
'@push.rocks/smartenv': 6.0.0
|
||||||
'@push.rocks/smartlog': 3.2.1
|
'@push.rocks/smartlog': 3.2.1
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
ws: 8.19.0
|
ws: 8.20.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
@@ -8033,8 +8027,6 @@ snapshots:
|
|||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
ws@8.19.0: {}
|
|
||||||
|
|
||||||
ws@8.20.0: {}
|
ws@8.20.0: {}
|
||||||
|
|
||||||
xml-parse-from-string@1.0.1: {}
|
xml-parse-from-string@1.0.1: {}
|
||||||
|
|||||||
418
test/test.websocket-e2e.ts
Normal file
418
test/test.websocket-e2e.ts
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { SmartProxy } from '../ts/index.js';
|
||||||
|
import * as http from 'http';
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws';
|
||||||
|
import { findFreePorts, assertPortsFree } from './helpers/port-allocator.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: create a WebSocket client that connects through the proxy.
|
||||||
|
* Registers the message handler BEFORE awaiting open to avoid race conditions.
|
||||||
|
*/
|
||||||
|
function connectWs(
|
||||||
|
url: string,
|
||||||
|
headers: Record<string, string> = {},
|
||||||
|
opts: WebSocket.ClientOptions = {},
|
||||||
|
): { ws: WebSocket; messages: string[]; opened: Promise<void> } {
|
||||||
|
const messages: string[] = [];
|
||||||
|
const ws = new WebSocket(url, { headers, ...opts });
|
||||||
|
|
||||||
|
// Register message handler immediately — before open fires
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
messages.push(data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
const opened = new Promise<void>((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => reject(new Error('WebSocket open timeout')), 5000);
|
||||||
|
ws.on('open', () => { clearTimeout(timeout); resolve(); });
|
||||||
|
ws.on('error', (err) => { clearTimeout(timeout); reject(err); });
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ws, messages, opened };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wait until `predicate` returns true, with a hard timeout. */
|
||||||
|
function waitFor(predicate: () => boolean, timeoutMs = 5000): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const deadline = setTimeout(() => reject(new Error('waitFor timeout')), timeoutMs);
|
||||||
|
const check = () => {
|
||||||
|
if (predicate()) { clearTimeout(deadline); resolve(); }
|
||||||
|
else setTimeout(check, 30);
|
||||||
|
};
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Graceful close helper */
|
||||||
|
function closeWs(ws: WebSocket): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (ws.readyState === WebSocket.CLOSED) return resolve();
|
||||||
|
ws.on('close', () => resolve());
|
||||||
|
ws.close();
|
||||||
|
setTimeout(resolve, 2000); // fallback
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Test 1: Basic WebSocket upgrade and bidirectional messaging ───
|
||||||
|
tap.test('should proxy WebSocket connections with bidirectional messaging', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
// Backend: echoes messages with prefix, sends greeting on connect
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer });
|
||||||
|
const backendMessages: string[] = [];
|
||||||
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
const msg = data.toString();
|
||||||
|
backendMessages.push(msg);
|
||||||
|
ws.send(`echo: ${msg}`);
|
||||||
|
});
|
||||||
|
ws.send('hello from backend');
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-test-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
// Connect client — message handler registered before open
|
||||||
|
const { ws, messages, opened } = connectWs(
|
||||||
|
`ws://127.0.0.1:${PROXY_PORT}/`,
|
||||||
|
{ Host: 'test.local' },
|
||||||
|
);
|
||||||
|
await opened;
|
||||||
|
|
||||||
|
// Wait for the backend greeting
|
||||||
|
await waitFor(() => messages.length >= 1);
|
||||||
|
expect(messages[0]).toEqual('hello from backend');
|
||||||
|
|
||||||
|
// Send 3 messages, expect 3 echoes
|
||||||
|
ws.send('ping 1');
|
||||||
|
ws.send('ping 2');
|
||||||
|
ws.send('ping 3');
|
||||||
|
|
||||||
|
await waitFor(() => messages.length >= 4);
|
||||||
|
|
||||||
|
expect(messages).toContain('echo: ping 1');
|
||||||
|
expect(messages).toContain('echo: ping 2');
|
||||||
|
expect(messages).toContain('echo: ping 3');
|
||||||
|
expect(backendMessages).toInclude('ping 1');
|
||||||
|
expect(backendMessages).toInclude('ping 2');
|
||||||
|
expect(backendMessages).toInclude('ping 3');
|
||||||
|
|
||||||
|
await closeWs(ws);
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Test 2: Multiple concurrent WebSocket connections ───
|
||||||
|
tap.test('should handle multiple concurrent WebSocket connections', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer });
|
||||||
|
|
||||||
|
let connectionCount = 0;
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
const id = ++connectionCount;
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
ws.send(`conn${id}: ${data.toString()}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-multi-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
const NUM_CLIENTS = 5;
|
||||||
|
const clients: { ws: WebSocket; messages: string[] }[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_CLIENTS; i++) {
|
||||||
|
const c = connectWs(
|
||||||
|
`ws://127.0.0.1:${PROXY_PORT}/`,
|
||||||
|
{ Host: 'test.local' },
|
||||||
|
);
|
||||||
|
await c.opened;
|
||||||
|
clients.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each client sends a unique message
|
||||||
|
for (let i = 0; i < NUM_CLIENTS; i++) {
|
||||||
|
clients[i].ws.send(`hello from client ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all replies
|
||||||
|
await waitFor(() => clients.every((c) => c.messages.length >= 1));
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_CLIENTS; i++) {
|
||||||
|
expect(clients[i].messages.length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(clients[i].messages[0]).toInclude(`hello from client ${i}`);
|
||||||
|
}
|
||||||
|
expect(connectionCount).toEqual(NUM_CLIENTS);
|
||||||
|
|
||||||
|
for (const c of clients) await closeWs(c.ws);
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Test 3: WebSocket with binary data ───
|
||||||
|
tap.test('should proxy binary WebSocket frames', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer });
|
||||||
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
ws.send(data, { binary: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-binary-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
const receivedBuffers: Buffer[] = [];
|
||||||
|
const ws = new WebSocket(`ws://127.0.0.1:${PROXY_PORT}/`, {
|
||||||
|
headers: { Host: 'test.local' },
|
||||||
|
});
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
receivedBuffers.push(Buffer.from(data as ArrayBuffer));
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => reject(new Error('timeout')), 5000);
|
||||||
|
ws.on('open', () => { clearTimeout(timeout); resolve(); });
|
||||||
|
ws.on('error', (err) => { clearTimeout(timeout); reject(err); });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a 256-byte buffer with known content
|
||||||
|
const sentBuffer = Buffer.alloc(256);
|
||||||
|
for (let i = 0; i < 256; i++) sentBuffer[i] = i;
|
||||||
|
ws.send(sentBuffer);
|
||||||
|
|
||||||
|
await waitFor(() => receivedBuffers.length >= 1);
|
||||||
|
|
||||||
|
expect(receivedBuffers[0].length).toEqual(256);
|
||||||
|
expect(Buffer.compare(receivedBuffers[0], sentBuffer)).toEqual(0);
|
||||||
|
|
||||||
|
await closeWs(ws);
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Test 4: WebSocket path and query string preserved ───
|
||||||
|
tap.test('should preserve path and query string through proxy', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer });
|
||||||
|
|
||||||
|
let receivedUrl = '';
|
||||||
|
wss.on('connection', (ws, req) => {
|
||||||
|
receivedUrl = req.url || '';
|
||||||
|
ws.send(`url: ${receivedUrl}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-path-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
const { ws, messages, opened } = connectWs(
|
||||||
|
`ws://127.0.0.1:${PROXY_PORT}/chat/room1?token=abc123`,
|
||||||
|
{ Host: 'test.local' },
|
||||||
|
);
|
||||||
|
await opened;
|
||||||
|
|
||||||
|
await waitFor(() => messages.length >= 1);
|
||||||
|
|
||||||
|
expect(receivedUrl).toEqual('/chat/room1?token=abc123');
|
||||||
|
expect(messages[0]).toEqual('url: /chat/room1?token=abc123');
|
||||||
|
|
||||||
|
await closeWs(ws);
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Test 5: Clean close propagation ───
|
||||||
|
tap.test('should handle clean WebSocket close from client', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer });
|
||||||
|
|
||||||
|
let backendGotClose = false;
|
||||||
|
let backendCloseCode = 0;
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
ws.on('close', (code) => {
|
||||||
|
backendGotClose = true;
|
||||||
|
backendCloseCode = code;
|
||||||
|
});
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
ws.send(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-close-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
const { ws, messages, opened } = connectWs(
|
||||||
|
`ws://127.0.0.1:${PROXY_PORT}/`,
|
||||||
|
{ Host: 'test.local' },
|
||||||
|
);
|
||||||
|
await opened;
|
||||||
|
|
||||||
|
// Confirm connection works with a round-trip
|
||||||
|
ws.send('test');
|
||||||
|
await waitFor(() => messages.length >= 1);
|
||||||
|
|
||||||
|
// Close with code 1000
|
||||||
|
let clientCloseCode = 0;
|
||||||
|
const closed = new Promise<void>((resolve) => {
|
||||||
|
ws.on('close', (code) => {
|
||||||
|
clientCloseCode = code;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
setTimeout(resolve, 3000);
|
||||||
|
});
|
||||||
|
ws.close(1000, 'done');
|
||||||
|
await closed;
|
||||||
|
|
||||||
|
// Wait for backend to register
|
||||||
|
await waitFor(() => backendGotClose, 3000);
|
||||||
|
|
||||||
|
expect(backendGotClose).toBeTrue();
|
||||||
|
expect(clientCloseCode).toEqual(1000);
|
||||||
|
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Test 6: Large messages ───
|
||||||
|
tap.test('should handle large WebSocket messages', async () => {
|
||||||
|
const [PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
||||||
|
|
||||||
|
const backendServer = http.createServer();
|
||||||
|
const wss = new WebSocketServer({ server: backendServer, maxPayload: 5 * 1024 * 1024 });
|
||||||
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
const buf = Buffer.from(data as ArrayBuffer);
|
||||||
|
ws.send(`received ${buf.length} bytes`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
backendServer.listen(BACKEND_PORT, '127.0.0.1', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
routes: [{
|
||||||
|
name: 'ws-large-route',
|
||||||
|
match: { ports: PROXY_PORT },
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
||||||
|
websocket: { enabled: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
const { ws, messages, opened } = connectWs(
|
||||||
|
`ws://127.0.0.1:${PROXY_PORT}/`,
|
||||||
|
{ Host: 'test.local' },
|
||||||
|
{ maxPayload: 5 * 1024 * 1024 },
|
||||||
|
);
|
||||||
|
await opened;
|
||||||
|
|
||||||
|
// Send a 1MB message
|
||||||
|
const largePayload = Buffer.alloc(1024 * 1024, 0x42);
|
||||||
|
ws.send(largePayload);
|
||||||
|
|
||||||
|
await waitFor(() => messages.length >= 1);
|
||||||
|
expect(messages[0]).toEqual(`received ${1024 * 1024} bytes`);
|
||||||
|
|
||||||
|
await closeWs(ws);
|
||||||
|
await proxy.stop();
|
||||||
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '27.2.0',
|
version: '27.3.0',
|
||||||
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user