418 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			418 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | # ws: a Node.js WebSocket library
 | ||
|  | 
 | ||
|  | [](https://www.npmjs.com/package/ws) | ||
|  | [](https://travis-ci.org/websockets/ws) | ||
|  | [](https://ci.appveyor.com/project/lpinca/ws) | ||
|  | [](https://coveralls.io/r/websockets/ws?branch=master) | ||
|  | 
 | ||
|  | ws is a simple to use, blazing fast, and thoroughly tested WebSocket client | ||
|  | and server implementation. | ||
|  | 
 | ||
|  | Passes the quite extensive Autobahn test suite: [server][server-report], | ||
|  | [client][client-report]. | ||
|  | 
 | ||
|  | **Note**: This module does not work in the browser. The client in the docs is a | ||
|  | reference to a back end with the role of a client in the WebSocket | ||
|  | communication. Browser clients must use the native | ||
|  | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. | ||
|  | To make the same code work seamlessly on Node.js and the browser, you can use | ||
|  | one of the many wrappers available on npm, like | ||
|  | [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). | ||
|  | 
 | ||
|  | ## Table of Contents
 | ||
|  | 
 | ||
|  | * [Protocol support](#protocol-support) | ||
|  | * [Installing](#installing) | ||
|  |   + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance) | ||
|  | * [API docs](#api-docs) | ||
|  | * [WebSocket compression](#websocket-compression) | ||
|  | * [Usage examples](#usage-examples) | ||
|  |   + [Sending and receiving text data](#sending-and-receiving-text-data) | ||
|  |   + [Sending binary data](#sending-binary-data) | ||
|  |   + [Simple server](#simple-server) | ||
|  |   + [External HTTP/S server](#external-https-server) | ||
|  |   + [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) | ||
|  |   + [Server broadcast](#server-broadcast) | ||
|  |   + [echo.websocket.org demo](#echowebsocketorg-demo) | ||
|  |   + [Other examples](#other-examples) | ||
|  | * [Error handling best practices](#error-handling-best-practices) | ||
|  | * [FAQ](#faq) | ||
|  |   + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) | ||
|  |   + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) | ||
|  |   + [How to connect via a proxy?](#how-to-connect-via-a-proxy) | ||
|  | * [Changelog](#changelog) | ||
|  | * [License](#license) | ||
|  | 
 | ||
|  | ## Protocol support
 | ||
|  | 
 | ||
|  | * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) | ||
|  | * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) | ||
|  | 
 | ||
|  | ## Installing
 | ||
|  | 
 | ||
|  | ``` | ||
|  | npm install --save ws | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Opt-in for performance and spec compliance
 | ||
|  | 
 | ||
|  | There are 2 optional modules that can be installed along side with the ws | ||
|  | module. These modules are binary addons which improve certain operations. | ||
|  | Prebuilt binaries are available for the most popular platforms so you don't | ||
|  | necessarily need to have a C++ compiler installed on your machine. | ||
|  | 
 | ||
|  | - `npm install --save-optional bufferutil`: Allows to efficiently perform | ||
|  |   operations such as masking and unmasking the data payload of the WebSocket | ||
|  |   frames. | ||
|  | - `npm install --save-optional utf-8-validate`: Allows to efficiently check | ||
|  |   if a message contains valid UTF-8 as required by the spec. | ||
|  | 
 | ||
|  | ## API docs
 | ||
|  | 
 | ||
|  | See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes. | ||
|  | 
 | ||
|  | ## WebSocket compression
 | ||
|  | 
 | ||
|  | ws supports the [permessage-deflate extension][permessage-deflate] which | ||
|  | enables the client and server to negotiate a compression algorithm and its | ||
|  | parameters, and then selectively apply it to the data payloads of each | ||
|  | WebSocket message. | ||
|  | 
 | ||
|  | The extension is disabled by default on the server and enabled by default on | ||
|  | the client. It adds a significant overhead in terms of performance and memory | ||
|  | consumption so we suggest to enable it only if it is really needed. | ||
|  | 
 | ||
|  | Note that Node.js has a variety of issues with high-performance compression, | ||
|  | where increased concurrency, especially on Linux, can lead to | ||
|  | [catastrophic memory fragmentation][node-zlib-bug] and slow performance. | ||
|  | If you intend to use permessage-deflate in production, it is worthwhile to set | ||
|  | up a test representative of your workload and ensure Node.js/zlib will handle | ||
|  | it with acceptable performance and memory usage. | ||
|  | 
 | ||
|  | Tuning of permessage-deflate can be done via the options defined below. You can | ||
|  | also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly | ||
|  | into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs]. | ||
|  | 
 | ||
|  | See [the docs][ws-server-options] for more options. | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const wss = new WebSocket.Server({ | ||
|  |   port: 8080, | ||
|  |   perMessageDeflate: { | ||
|  |     zlibDeflateOptions: { // See zlib defaults. | ||
|  |       chunkSize: 1024, | ||
|  |       memLevel: 7, | ||
|  |       level: 3, | ||
|  |     }, | ||
|  |     zlibInflateOptions: { | ||
|  |       chunkSize: 10 * 1024 | ||
|  |     }, | ||
|  |     // Other options settable: | ||
|  |     clientNoContextTakeover: true, // Defaults to negotiated value. | ||
|  |     serverNoContextTakeover: true, // Defaults to negotiated value. | ||
|  |     clientMaxWindowBits: 10,       // Defaults to negotiated value. | ||
|  |     serverMaxWindowBits: 10,       // Defaults to negotiated value. | ||
|  |     // Below options specified as default values. | ||
|  |     concurrencyLimit: 10,          // Limits zlib concurrency for perf. | ||
|  |     threshold: 1024,               // Size (in bytes) below which messages | ||
|  |                                    // should not be compressed. | ||
|  |   } | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | The client will only use the extension if it is supported and enabled on the | ||
|  | server. To always disable the extension on the client set the | ||
|  | `perMessageDeflate` option to `false`. | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const ws = new WebSocket('ws://www.host.com/path', { | ||
|  |   perMessageDeflate: false | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ## Usage examples
 | ||
|  | 
 | ||
|  | ### Sending and receiving text data
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const ws = new WebSocket('ws://www.host.com/path'); | ||
|  | 
 | ||
|  | ws.on('open', function open() { | ||
|  |   ws.send('something'); | ||
|  | }); | ||
|  | 
 | ||
|  | ws.on('message', function incoming(data) { | ||
|  |   console.log(data); | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Sending binary data
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const ws = new WebSocket('ws://www.host.com/path'); | ||
|  | 
 | ||
|  | ws.on('open', function open() { | ||
|  |   const array = new Float32Array(5); | ||
|  | 
 | ||
|  |   for (var i = 0; i < array.length; ++i) { | ||
|  |     array[i] = i / 2; | ||
|  |   } | ||
|  | 
 | ||
|  |   ws.send(array); | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Simple server
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const wss = new WebSocket.Server({ port: 8080 }); | ||
|  | 
 | ||
|  | wss.on('connection', function connection(ws) { | ||
|  |   ws.on('message', function incoming(message) { | ||
|  |     console.log('received: %s', message); | ||
|  |   }); | ||
|  | 
 | ||
|  |   ws.send('something'); | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### External HTTP/S server
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const fs = require('fs'); | ||
|  | const https = require('https'); | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const server = new https.createServer({ | ||
|  |   cert: fs.readFileSync('/path/to/cert.pem'), | ||
|  |   key: fs.readFileSync('/path/to/key.pem') | ||
|  | }); | ||
|  | const wss = new WebSocket.Server({ server }); | ||
|  | 
 | ||
|  | wss.on('connection', function connection(ws) { | ||
|  |   ws.on('message', function incoming(message) { | ||
|  |     console.log('received: %s', message); | ||
|  |   }); | ||
|  | 
 | ||
|  |   ws.send('something'); | ||
|  | }); | ||
|  | 
 | ||
|  | server.listen(8080); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Multiple servers sharing a single HTTP/S server
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const http = require('http'); | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const server = http.createServer(); | ||
|  | const wss1 = new WebSocket.Server({ noServer: true }); | ||
|  | const wss2 = new WebSocket.Server({ noServer: true }); | ||
|  | 
 | ||
|  | wss1.on('connection', function connection(ws) { | ||
|  |   // ... | ||
|  | }); | ||
|  | 
 | ||
|  | wss2.on('connection', function connection(ws) { | ||
|  |   // ... | ||
|  | }); | ||
|  | 
 | ||
|  | server.on('upgrade', function upgrade(request, socket, head) { | ||
|  |   const pathname = url.parse(request.url).pathname; | ||
|  | 
 | ||
|  |   if (pathname === '/foo') { | ||
|  |     wss1.handleUpgrade(request, socket, head, function done(ws) { | ||
|  |       wss1.emit('connection', ws, request); | ||
|  |     }); | ||
|  |   } else if (pathname === '/bar') { | ||
|  |     wss2.handleUpgrade(request, socket, head, function done(ws) { | ||
|  |       wss2.emit('connection', ws, request); | ||
|  |     }); | ||
|  |   } else { | ||
|  |     socket.destroy(); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | server.listen(8080); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Server broadcast
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const wss = new WebSocket.Server({ port: 8080 }); | ||
|  | 
 | ||
|  | // Broadcast to all. | ||
|  | wss.broadcast = function broadcast(data) { | ||
|  |   wss.clients.forEach(function each(client) { | ||
|  |     if (client.readyState === WebSocket.OPEN) { | ||
|  |       client.send(data); | ||
|  |     } | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | wss.on('connection', function connection(ws) { | ||
|  |   ws.on('message', function incoming(data) { | ||
|  |     // Broadcast to everyone else. | ||
|  |     wss.clients.forEach(function each(client) { | ||
|  |       if (client !== ws && client.readyState === WebSocket.OPEN) { | ||
|  |         client.send(data); | ||
|  |       } | ||
|  |     }); | ||
|  |   }); | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### echo.websocket.org demo
 | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const ws = new WebSocket('wss://echo.websocket.org/', { | ||
|  |   origin: 'https://websocket.org' | ||
|  | }); | ||
|  | 
 | ||
|  | ws.on('open', function open() { | ||
|  |   console.log('connected'); | ||
|  |   ws.send(Date.now()); | ||
|  | }); | ||
|  | 
 | ||
|  | ws.on('close', function close() { | ||
|  |   console.log('disconnected'); | ||
|  | }); | ||
|  | 
 | ||
|  | ws.on('message', function incoming(data) { | ||
|  |   console.log(`Roundtrip time: ${Date.now() - data} ms`); | ||
|  | 
 | ||
|  |   setTimeout(function timeout() { | ||
|  |     ws.send(Date.now()); | ||
|  |   }, 500); | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### Other examples
 | ||
|  | 
 | ||
|  | For a full example with a browser client communicating with a ws server, see the | ||
|  | examples folder. | ||
|  | 
 | ||
|  | Otherwise, see the test cases. | ||
|  | 
 | ||
|  | ## Error handling best practices
 | ||
|  | 
 | ||
|  | ```js | ||
|  | // If the WebSocket is closed before the following send is attempted | ||
|  | ws.send('something'); | ||
|  | 
 | ||
|  | // Errors (both immediate and async write errors) can be detected in an optional | ||
|  | // callback. The callback is also the only way of being notified that data has | ||
|  | // actually been sent. | ||
|  | ws.send('something', function ack(error) { | ||
|  |   // If error is not defined, the send has been completed, otherwise the error | ||
|  |   // object will indicate what failed. | ||
|  | }); | ||
|  | 
 | ||
|  | // Immediate errors can also be handled with `try...catch`, but **note** that | ||
|  | // since sends are inherently asynchronous, socket write failures will *not* be | ||
|  | // captured when this technique is used. | ||
|  | try { ws.send('something'); } | ||
|  | catch (e) { /* handle error */ } | ||
|  | ``` | ||
|  | 
 | ||
|  | ## FAQ
 | ||
|  | 
 | ||
|  | ### How to get the IP address of the client?
 | ||
|  | 
 | ||
|  | The remote IP address can be obtained from the raw socket. | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const wss = new WebSocket.Server({ port: 8080 }); | ||
|  | 
 | ||
|  | wss.on('connection', function connection(ws, req) { | ||
|  |   const ip = req.connection.remoteAddress; | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | When the server runs behind a proxy like NGINX, the de-facto standard is to use | ||
|  | the `X-Forwarded-For` header. | ||
|  | 
 | ||
|  | ```js | ||
|  | wss.on('connection', function connection(ws, req) { | ||
|  |   const ip = req.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; | ||
|  | }); | ||
|  | ``` | ||
|  | 
 | ||
|  | ### How to detect and close broken connections?
 | ||
|  | 
 | ||
|  | Sometimes the link between the server and the client can be interrupted in a | ||
|  | way that keeps both the server and the client unaware of the broken state of the | ||
|  | connection (e.g. when pulling the cord). | ||
|  | 
 | ||
|  | In these cases ping messages can be used as a means to verify that the remote | ||
|  | endpoint is still responsive. | ||
|  | 
 | ||
|  | ```js | ||
|  | const WebSocket = require('ws'); | ||
|  | 
 | ||
|  | const wss = new WebSocket.Server({ port: 8080 }); | ||
|  | 
 | ||
|  | function noop() {} | ||
|  | 
 | ||
|  | function heartbeat() { | ||
|  |   this.isAlive = true; | ||
|  | } | ||
|  | 
 | ||
|  | wss.on('connection', function connection(ws) { | ||
|  |   ws.isAlive = true; | ||
|  |   ws.on('pong', heartbeat); | ||
|  | }); | ||
|  | 
 | ||
|  | const interval = setInterval(function ping() { | ||
|  |   wss.clients.forEach(function each(ws) { | ||
|  |     if (ws.isAlive === false) return ws.terminate(); | ||
|  | 
 | ||
|  |     ws.isAlive = false; | ||
|  |     ws.ping(noop); | ||
|  |   }); | ||
|  | }, 30000); | ||
|  | ``` | ||
|  | 
 | ||
|  | Pong messages are automatically sent in response to ping messages as required | ||
|  | by the spec. | ||
|  | 
 | ||
|  | ### How to connect via a proxy?
 | ||
|  | 
 | ||
|  | Use a custom `http.Agent` implementation like [https-proxy-agent][] or | ||
|  | [socks-proxy-agent][]. | ||
|  | 
 | ||
|  | ## Changelog
 | ||
|  | 
 | ||
|  | We're using the GitHub [releases][changelog] for changelog entries. | ||
|  | 
 | ||
|  | ## License
 | ||
|  | 
 | ||
|  | [MIT](LICENSE) | ||
|  | 
 | ||
|  | [https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent | ||
|  | [socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent | ||
|  | [client-report]: http://websockets.github.io/ws/autobahn/clients/ | ||
|  | [server-report]: http://websockets.github.io/ws/autobahn/servers/ | ||
|  | [permessage-deflate]: https://tools.ietf.org/html/rfc7692 | ||
|  | [changelog]: https://github.com/websockets/ws/releases | ||
|  | [node-zlib-bug]: https://github.com/nodejs/node/issues/8871 | ||
|  | [node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options | ||
|  | [ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback |