示例
socket.io-client 4.8.3: Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing
已验证示例 — npm socket.io-client 4.8.3: Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing.…
sha256:a34e5524e2d70aa4a594e6e5c07de316ebbcb9ce2828e6283a63ad65c5913e95
本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。
提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
MIT-0
执行证据
声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。
- 证据依据
- 签名契约通过
- 验证回执
- 3
- 构建过它的签名密钥
- 3
声明的环境
node linux x64 node node npm
验证运行环境
| 环境 | 契约 | 阶段 | 运行日期 |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-15 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
| node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5… |
2026-09-07 |
案例
HOW- 目标
- Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing
- 符号
-
- io
- WebSocketServer
- 环境
- node
- 创建时间
- 2026-08-16T06:30:54Z
契约
- assert default socket.io-client connection fails with xhr poll error 426 when pointed at a plain WebSocket server
- assert websocket-only socket.io-client fails with server error when plain WebSocket server sends raw text or JSON without Engine.IO packet 0 OPEN
- assert websocket-only socket.io-client times out when plain WebSocket server accepts connection but stays silent
- assert socket.io-client requires Engine.IO OPEN packet 0 and Socket.IO CONNECT ACK packet 40 to emit connect, and frames event messages as 42[eventName, payload]
文件
- NOTES.md
- csx.json
- package-lock.json
- package.json
- test/contract.mjs
源代码
# NOTES
## `search_known_solution` Result
`search_known_solution` returned `MATCH: COMPATIBLE` (low confidence) pointing to generic `ws` echo and API distinction samples (`sha256:91b961d23ba84c3205b8885fc80b5203a7d47a923e7a8aab6729d513e2a66a95` and `sha256:1a1d36a350a9eb2dcd345ed3b5163cee5cc2ec75783de4b1c90f8d313b012ee7`), with NO sample addressing the protocol incompatibility between `socket.io-client` and plain WebSocket servers.
## What a naive model would write instead
A naive model assumes `socket.io-client` is a generic WebSocket client and connects directly via `io("ws://localhost:PORT")` or `io("http://localhost:PORT", { transports: ["websocket"] })`, expecting standard event listener callbacks (`socket.on("message", ...)`) to receive plain strings or JSON objects emitted by a plain WebSocket (`ws`) server.
## How the wrong version fails
It fails loudly at the protocol handshake layer: by default, HTTP polling fails with `xhr poll error` (HTTP 426 / 404); with forced WebSocket transport, the client aborts with `server error` on un-framed data or hangs until `timeout` because Engine.IO packet `0` (OPEN) and Socket.IO packet `40` (CONNECT ACK) are never received, preventing `socket.connected` from ever becoming `true` and buffering all emitted messages without transmitting them.
{"case":{"caseId":"case:sha256:fac86b17c36bfe76ac35e52f0014551563313b4841d0aa7717153895b88747a4","constraints":{"runtime":"node"},"contract":["assert default socket.io-client connection fails with xhr poll error 426 when pointed at a plain WebSocket server","assert websocket-only socket.io-client fails with server error when plain WebSocket server sends raw text or JSON without Engine.IO packet 0 OPEN","assert websocket-only socket.io-client times out when plain WebSocket server accepts connection but stays silent","assert socket.io-client requires Engine.IO OPEN packet 0 and Socket.IO CONNECT ACK packet 40 to emit connect, and frames event messages as 42[eventName, payload]"],"goal":"Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing","kind":"HOW","packages":["pkg:npm/socket.io-client@4.8.3","pkg:npm/ws@8.21.3"],"schemaVersion":1,"symbols":["io","WebSocketServer"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/socket.io-client@4.8.3","pkg:npm/ws@8.21.3"],"schemaVersion":1,"symbols":["io","WebSocketServer"],"verifierAdapter":"node-typescript@1"}
{
"name": "sample",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sample",
"version": "1.0.0",
"license": "MIT-0",
"dependencies": {
"socket.io-client": "4.8.3",
"ws": "8.21.3"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-client": {
"version": "6.6.6",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz",
"integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.21.0",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ws": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"license": "MIT",
"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
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
}
}
}
{
"name": "sample",
"version": "1.0.0",
"type": "module",
"license": "MIT-0",
"dependencies": {
"socket.io-client": "4.8.3",
"ws": "8.21.3"
}
}
import assert from 'node:assert/strict';
import { WebSocketServer } from 'ws';
import { io } from 'socket.io-client';
async function main() {
// --- Test 1: Default transport (HTTP polling first) against plain WebSocket server ---
{
const wss = new WebSocketServer({ port: 0 });
await new Promise(r => wss.on('listening', r));
const port = wss.address().port;
const socket = io(`http://127.0.0.1:${port}`, {
timeout: 1000,
reconnection: false,
});
const err = await new Promise((resolve) => {
socket.on('connect_error', resolve);
});
assert.equal(socket.connected, false, 'Socket.IO client must not connect to plain WS server');
assert.equal(err.message, 'xhr poll error', 'Default transport fails at HTTP polling handshake');
assert.equal(err.description, 426, 'Plain WS server returns HTTP 426 Upgrade Required for polling GET');
socket.close();
await new Promise(r => wss.close(r));
}
// --- Test 2: Forced WebSocket transport against plain WS server sending raw text/JSON ---
{
const wss = new WebSocketServer({ port: 0 });
await new Promise(r => wss.on('listening', r));
const port = wss.address().port;
let serverReceivedMessages = [];
wss.on('connection', (ws) => {
// Plain WS server sends raw application JSON without Engine.IO packet header
ws.send(JSON.stringify({ type: 'greeting', payload: 'hello' }));
ws.on('message', (msg) => serverReceivedMessages.push(msg.toString()));
});
const socket = io(`ws://127.0.0.1:${port}`, {
transports: ['websocket'],
timeout: 1000,
reconnection: false,
});
socket.emit('chat', 'this should never reach server');
const err = await new Promise((resolve) => {
socket.on('connect_error', resolve);
});
assert.equal(socket.connected, false, 'Socket must not be connected when raw data received');
assert.equal(err.message, 'server error', 'Engine.IO parser fails on non-Engine.IO packet type prefix');
assert.equal(serverReceivedMessages.length, 0, 'Socket.IO client buffers events and sends nothing before handshake');
socket.close();
await new Promise(r => wss.close(r));
}
// --- Test 3: Forced WebSocket transport against silent plain WS server ---
{
const wss = new WebSocketServer({ port: 0 });
await new Promise(r => wss.on('listening', r));
const port = wss.address().port;
wss.on('connection', (_ws) => {
// Server accepts WebSocket upgrade but never sends Engine.IO packet 0 OPEN
});
const socket = io(`ws://127.0.0.1:${port}`, {
transports: ['websocket'],
timeout: 300,
reconnection: false,
});
const err = await new Promise((resolve) => {
socket.on('connect_error', resolve);
});
assert.equal(socket.connected, false, 'Socket remains disconnected while waiting for handshake');
assert.equal(err.message, 'timeout', 'Client times out waiting for Engine.IO OPEN packet 0');
socket.close();
await new Promise(r => wss.close(r));
}
// --- Test 4: Wire protocol verification (Engine.IO 4 + Socket.IO v4 wire framing) ---
{
const wss = new WebSocketServer({ port: 0 });
await new Promise(r => wss.on('listening', r));
const port = wss.address().port;
const wirePacketsReceivedByServer = [];
wss.on('connection', (ws) => {
// Step 1: Server must send Engine.IO packet '0' (OPEN) with session metadata
ws.send('0' + JSON.stringify({
sid: 'sess-abc-123',
upgrades: [],
pingInterval: 25000,
pingTimeout: 20000,
maxPayload: 1000000,
}));
ws.on('message', (data) => {
const raw = data.toString();
wirePacketsReceivedByServer.push(raw);
// Step 2: Client sends Socket.IO namespace CONNECT packet '40'
if (raw === '40') {
// Server acknowledges root namespace connection
ws.send('40{"sid":"sock-xyz-789"}');
} else if (raw.startsWith('42')) {
// Step 3: Client sends event '42["custom_event",{"x":1}]'
// Server sends back event '42["server_reply",{"status":"ok"}]'
ws.send('42["server_reply",{"status":"ok"}]');
}
});
});
const socket = io(`ws://127.0.0.1:${port}`, {
transports: ['websocket'],
reconnection: false,
});
await new Promise((resolve, reject) => {
socket.on('connect', resolve);
socket.on('connect_error', reject);
});
assert.equal(socket.connected, true, 'Socket connects once Engine.IO 0 and Socket.IO 40 are satisfied');
assert.equal(socket.id, 'sock-xyz-789', 'Socket ID matches Socket.IO namespace handshake sid');
const serverReplyPromise = new Promise((resolve) => {
socket.on('server_reply', resolve);
});
socket.emit('custom_event', { x: 1 });
const reply = await serverReplyPromise;
assert.deepEqual(reply, { status: 'ok' }, 'Client parsed Socket.IO event payload successfully');
// Verify wire framing sent over raw WebSocket
assert.equal(wirePacketsReceivedByServer[0], '40', 'Client must send Engine.IO 4 (MESSAGE) + Socket.IO 0 (CONNECT)');
assert.equal(wirePacketsReceivedByServer[1], '42["custom_event",{"x":1}]', 'Client must frame events as 42[eventName, data]');
socket.close();
await new Promise(r => wss.close(r));
}
}
main().then(() => {
process.exit(0);
}).catch((err) => {
console.error('Contract assertion failed:', err);
process.exit(1);
});