UNPKG

15.3 kBMarkdownView Raw
1# ws: a Node.js WebSocket library
2
3[![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws)
4[![CI](https://img.shields.io/github/actions/workflow/status/websockets/ws/ci.yml?branch=master&label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
5[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg?logo=coveralls)](https://coveralls.io/github/websockets/ws)
6
7ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and
8server implementation.
9
10Passes the quite extensive Autobahn test suite: [server][server-report],
11[client][client-report].
12
13**Note**: This module does not work in the browser. The client in the docs is a
14reference to a backend with the role of a client in the WebSocket communication.
15Browser clients must use the native
16[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
17object. To make the same code work seamlessly on Node.js and the browser, you
18can use one of the many wrappers available on npm, like
19[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).
20
21## Table of Contents
22
23- [Protocol support](#protocol-support)
24- [Installing](#installing)
25 - [Opt-in for performance](#opt-in-for-performance)
26 - [Legacy opt-in for performance](#legacy-opt-in-for-performance)
27- [API docs](#api-docs)
28- [WebSocket compression](#websocket-compression)
29- [Usage examples](#usage-examples)
30 - [Sending and receiving text data](#sending-and-receiving-text-data)
31 - [Sending binary data](#sending-binary-data)
32 - [Simple server](#simple-server)
33 - [External HTTP/S server](#external-https-server)
34 - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)
35 - [Client authentication](#client-authentication)
36 - [Server broadcast](#server-broadcast)
37 - [Round-trip time](#round-trip-time)
38 - [Use the Node.js streams API](#use-the-nodejs-streams-api)
39 - [Other examples](#other-examples)
40- [FAQ](#faq)
41 - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
42 - [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
43 - [How to connect via a proxy?](#how-to-connect-via-a-proxy)
44- [Changelog](#changelog)
45- [License](#license)
46
47## Protocol support
48
49- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
50- **HyBi drafts 13-17** (Current default, alternatively option
51 `protocolVersion: 13`)
52
53## Installing
54
55```
56npm install ws
57```
58
59### Opt-in for performance
60
61[bufferutil][] is an optional module that can be installed alongside the ws
62module:
63
64```
65npm install --save-optional bufferutil
66```
67
68This is a binary addon that improves the performance of certain operations such
69as masking and unmasking the data payload of the WebSocket frames. Prebuilt
70binaries are available for the most popular platforms, so you don't necessarily
71need to have a C++ compiler installed on your machine.
72
73To force ws to not use bufferutil, use the
74[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This
75can be useful to enhance security in systems where a user can put a package in
76the package search path of an application of another user, due to how the
77Node.js resolver algorithm works.
78
79#### Legacy opt-in for performance
80
81If you are running on an old version of Node.js (prior to v18.14.0), ws also
82supports the [utf-8-validate][] module:
83
84```
85npm install --save-optional utf-8-validate
86```
87
88This contains a binary polyfill for [`buffer.isUtf8()`][].
89
90To force ws not to use utf-8-validate, use the
91[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.
92
93## API docs
94
95See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and
96utility functions.
97
98## WebSocket compression
99
100ws supports the [permessage-deflate extension][permessage-deflate] which enables
101the client and server to negotiate a compression algorithm and its parameters,
102and then selectively apply it to the data payloads of each WebSocket message.
103
104The extension is disabled by default on the server and enabled by default on the
105client. It adds a significant overhead in terms of performance and memory
106consumption so we suggest to enable it only if it is really needed.
107
108Note that Node.js has a variety of issues with high-performance compression,
109where increased concurrency, especially on Linux, can lead to [catastrophic
110memory fragmentation][node-zlib-bug] and slow performance. If you intend to use
111permessage-deflate in production, it is worthwhile to set up a test
112representative of your workload and ensure Node.js/zlib will handle it with
113acceptable performance and memory usage.
114
115Tuning of permessage-deflate can be done via the options defined below. You can
116also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly
117into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].
118
119See [the docs][ws-server-options] for more options.
120
121```js
122import WebSocket, { WebSocketServer } from 'ws';
123
124const wss = new WebSocketServer({
125 port: 8080,
126 perMessageDeflate: {
127 zlibDeflateOptions: {
128 // See zlib defaults.
129 chunkSize: 1024,
130 memLevel: 7,
131 level: 3
132 },
133 zlibInflateOptions: {
134 chunkSize: 10 * 1024
135 },
136 // Other options settable:
137 clientNoContextTakeover: true, // Defaults to negotiated value.
138 serverNoContextTakeover: true, // Defaults to negotiated value.
139 serverMaxWindowBits: 10, // Defaults to negotiated value.
140 // Below options specified as default values.
141 concurrencyLimit: 10, // Limits zlib concurrency for perf.
142 threshold: 1024 // Size (in bytes) below which messages
143 // should not be compressed if context takeover is disabled.
144 }
145});
146```
147
148The client will only use the extension if it is supported and enabled on the
149server. To always disable the extension on the client, set the
150`perMessageDeflate` option to `false`.
151
152```js
153import WebSocket from 'ws';
154
155const ws = new WebSocket('ws://www.host.com/path', {
156 perMessageDeflate: false
157});
158```
159
160## Usage examples
161
162### Sending and receiving text data
163
164```js
165import WebSocket from 'ws';
166
167const ws = new WebSocket('ws://www.host.com/path');
168
169ws.on('error', console.error);
170
171ws.on('open', function open() {
172 ws.send('something');
173});
174
175ws.on('message', function message(data) {
176 console.log('received: %s', data);
177});
178```
179
180### Sending binary data
181
182```js
183import WebSocket from 'ws';
184
185const ws = new WebSocket('ws://www.host.com/path');
186
187ws.on('error', console.error);
188
189ws.on('open', function open() {
190 const array = new Float32Array(5);
191
192 for (var i = 0; i < array.length; ++i) {
193 array[i] = i / 2;
194 }
195
196 ws.send(array);
197});
198```
199
200### Simple server
201
202```js
203import { WebSocketServer } from 'ws';
204
205const wss = new WebSocketServer({ port: 8080 });
206
207wss.on('connection', function connection(ws) {
208 ws.on('error', console.error);
209
210 ws.on('message', function message(data) {
211 console.log('received: %s', data);
212 });
213
214 ws.send('something');
215});
216```
217
218### External HTTP/S server
219
220```js
221import { createServer } from 'https';
222import { readFileSync } from 'fs';
223import { WebSocketServer } from 'ws';
224
225const server = createServer({
226 cert: readFileSync('/path/to/cert.pem'),
227 key: readFileSync('/path/to/key.pem')
228});
229const wss = new WebSocketServer({ server });
230
231wss.on('connection', function connection(ws) {
232 ws.on('error', console.error);
233
234 ws.on('message', function message(data) {
235 console.log('received: %s', data);
236 });
237
238 ws.send('something');
239});
240
241server.listen(8080);
242```
243
244### Multiple servers sharing a single HTTP/S server
245
246```js
247import { createServer } from 'http';
248import { WebSocketServer } from 'ws';
249
250const server = createServer();
251const wss1 = new WebSocketServer({ noServer: true });
252const wss2 = new WebSocketServer({ noServer: true });
253
254wss1.on('connection', function connection(ws) {
255 ws.on('error', console.error);
256
257 // ...
258});
259
260wss2.on('connection', function connection(ws) {
261 ws.on('error', console.error);
262
263 // ...
264});
265
266server.on('upgrade', function upgrade(request, socket, head) {
267 const { pathname } = new URL(request.url, 'wss://base.url');
268
269 if (pathname === '/foo') {
270 wss1.handleUpgrade(request, socket, head, function done(ws) {
271 wss1.emit('connection', ws, request);
272 });
273 } else if (pathname === '/bar') {
274 wss2.handleUpgrade(request, socket, head, function done(ws) {
275 wss2.emit('connection', ws, request);
276 });
277 } else {
278 socket.destroy();
279 }
280});
281
282server.listen(8080);
283```
284
285### Client authentication
286
287```js
288import { createServer } from 'http';
289import { WebSocketServer } from 'ws';
290
291function onSocketError(err) {
292 console.error(err);
293}
294
295const server = createServer();
296const wss = new WebSocketServer({ noServer: true });
297
298wss.on('connection', function connection(ws, request, client) {
299 ws.on('error', console.error);
300
301 ws.on('message', function message(data) {
302 console.log(`Received message ${data} from user ${client}`);
303 });
304});
305
306server.on('upgrade', function upgrade(request, socket, head) {
307 socket.on('error', onSocketError);
308
309 // This function is not defined on purpose. Implement it with your own logic.
310 authenticate(request, function next(err, client) {
311 if (err || !client) {
312 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
313 socket.destroy();
314 return;
315 }
316
317 socket.removeListener('error', onSocketError);
318
319 wss.handleUpgrade(request, socket, head, function done(ws) {
320 wss.emit('connection', ws, request, client);
321 });
322 });
323});
324
325server.listen(8080);
326```
327
328Also see the provided [example][session-parse-example] using `express-session`.
329
330### Server broadcast
331
332A client WebSocket broadcasting to all connected WebSocket clients, including
333itself.
334
335```js
336import WebSocket, { WebSocketServer } from 'ws';
337
338const wss = new WebSocketServer({ port: 8080 });
339
340wss.on('connection', function connection(ws) {
341 ws.on('error', console.error);
342
343 ws.on('message', function message(data, isBinary) {
344 wss.clients.forEach(function each(client) {
345 if (client.readyState === WebSocket.OPEN) {
346 client.send(data, { binary: isBinary });
347 }
348 });
349 });
350});
351```
352
353A client WebSocket broadcasting to every other connected WebSocket clients,
354excluding itself.
355
356```js
357import WebSocket, { WebSocketServer } from 'ws';
358
359const wss = new WebSocketServer({ port: 8080 });
360
361wss.on('connection', function connection(ws) {
362 ws.on('error', console.error);
363
364 ws.on('message', function message(data, isBinary) {
365 wss.clients.forEach(function each(client) {
366 if (client !== ws && client.readyState === WebSocket.OPEN) {
367 client.send(data, { binary: isBinary });
368 }
369 });
370 });
371});
372```
373
374### Round-trip time
375
376```js
377import WebSocket from 'ws';
378
379const ws = new WebSocket('wss://websocket-echo.com/');
380
381ws.on('error', console.error);
382
383ws.on('open', function open() {
384 console.log('connected');
385 ws.send(Date.now());
386});
387
388ws.on('close', function close() {
389 console.log('disconnected');
390});
391
392ws.on('message', function message(data) {
393 console.log(`Round-trip time: ${Date.now() - data} ms`);
394
395 setTimeout(function timeout() {
396 ws.send(Date.now());
397 }, 500);
398});
399```
400
401### Use the Node.js streams API
402
403```js
404import WebSocket, { createWebSocketStream } from 'ws';
405
406const ws = new WebSocket('wss://websocket-echo.com/');
407
408const duplex = createWebSocketStream(ws, { encoding: 'utf8' });
409
410duplex.on('error', console.error);
411
412duplex.pipe(process.stdout);
413process.stdin.pipe(duplex);
414```
415
416### Other examples
417
418For a full example with a browser client communicating with a ws server, see the
419examples folder.
420
421Otherwise, see the test cases.
422
423## FAQ
424
425### How to get the IP address of the client?
426
427The remote IP address can be obtained from the raw socket.
428
429```js
430import { WebSocketServer } from 'ws';
431
432const wss = new WebSocketServer({ port: 8080 });
433
434wss.on('connection', function connection(ws, req) {
435 const ip = req.socket.remoteAddress;
436
437 ws.on('error', console.error);
438});
439```
440
441When the server runs behind a proxy like NGINX, the de-facto standard is to use
442the `X-Forwarded-For` header.
443
444```js
445wss.on('connection', function connection(ws, req) {
446 const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
447
448 ws.on('error', console.error);
449});
450```
451
452### How to detect and close broken connections?
453
454Sometimes, the link between the server and the client can be interrupted in a
455way that keeps both the server and the client unaware of the broken state of the
456connection (e.g. when pulling the cord).
457
458In these cases, ping messages can be used as a means to verify that the remote
459endpoint is still responsive.
460
461```js
462import { WebSocketServer } from 'ws';
463
464function heartbeat() {
465 this.isAlive = true;
466}
467
468const wss = new WebSocketServer({ port: 8080 });
469
470wss.on('connection', function connection(ws) {
471 ws.isAlive = true;
472 ws.on('error', console.error);
473 ws.on('pong', heartbeat);
474});
475
476const interval = setInterval(function ping() {
477 wss.clients.forEach(function each(ws) {
478 if (ws.isAlive === false) return ws.terminate();
479
480 ws.isAlive = false;
481 ws.ping();
482 });
483}, 30000);
484
485wss.on('close', function close() {
486 clearInterval(interval);
487});
488```
489
490Pong messages are automatically sent in response to ping messages as required by
491the spec.
492
493Just like the server example above, your clients might as well lose connection
494without knowing it. You might want to add a ping listener on your clients to
495prevent that. A simple implementation would be:
496
497```js
498import WebSocket from 'ws';
499
500function heartbeat() {
501 clearTimeout(this.pingTimeout);
502
503 // Use `WebSocket#terminate()`, which immediately destroys the connection,
504 // instead of `WebSocket#close()`, which waits for the close timer.
505 // Delay should be equal to the interval at which your server
506 // sends out pings plus a conservative assumption of the latency.
507 this.pingTimeout = setTimeout(() => {
508 this.terminate();
509 }, 30000 + 1000);
510}
511
512const client = new WebSocket('wss://websocket-echo.com/');
513
514client.on('error', console.error);
515client.on('open', heartbeat);
516client.on('ping', heartbeat);
517client.on('close', function clear() {
518 clearTimeout(this.pingTimeout);
519});
520```
521
522### How to connect via a proxy?
523
524Use a custom `http.Agent` implementation like [https-proxy-agent][] or
525[socks-proxy-agent][].
526
527## Changelog
528
529We're using the GitHub [releases][changelog] for changelog entries.
530
531## License
532
533[MIT](LICENSE)
534
535[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input
536[bufferutil]: https://github.com/websockets/bufferutil
537[changelog]: https://github.com/websockets/ws/releases
538[client-report]: http://websockets.github.io/ws/autobahn/clients/
539[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
540[node-zlib-bug]: https://github.com/nodejs/node/issues/8871
541[node-zlib-deflaterawdocs]:
542 https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
543[permessage-deflate]: https://tools.ietf.org/html/rfc7692
544[server-report]: http://websockets.github.io/ws/autobahn/servers/
545[session-parse-example]: ./examples/express-session-parse
546[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
547[utf-8-validate]: https://github.com/websockets/utf-8-validate
548[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback