Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js

Overview

ws: a Node.js WebSocket library

Version npm CI Coverage Status

ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation.

Passes the quite extensive Autobahn test suite: server, client.

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 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.

Table of Contents

Protocol support

  • HyBi drafts 07-12 (Use the option protocolVersion: 8)
  • HyBi drafts 13-17 (Current default, alternatively option protocolVersion: 13)

Installing

npm install ws

Opt-in for performance

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.

API docs

See /doc/ws.md for Node.js-like documentation of ws classes and utility functions.

WebSocket compression

ws supports the permessage-deflate extension 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 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.

See the docs for more options.

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({
  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.
    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 if context takeover is disabled.
  }
});

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.

import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path', {
  perMessageDeflate: false
});

Usage examples

Sending and receiving text data

import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path');

ws.on('open', function open() {
  ws.send('something');
});

ws.on('message', function incoming(message) {
  console.log('received: %s', message);
});

Sending binary data

import WebSocket from '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

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ 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

import { createServer } from 'https';
import { readFileSync } from 'fs';
import { WebSocketServer } from 'ws';

const server = createServer({
  cert: readFileSync('/path/to/cert.pem'),
  key: readFileSync('/path/to/key.pem')
});
const wss = new WebSocketServer({ 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

import { createServer } from 'http';
import { parse } from 'url';
import { WebSocketServer } from 'ws';

const server = createServer();
const wss1 = new WebSocketServer({ noServer: true });
const wss2 = new WebSocketServer({ noServer: true });

wss1.on('connection', function connection(ws) {
  // ...
});

wss2.on('connection', function connection(ws) {
  // ...
});

server.on('upgrade', function upgrade(request, socket, head) {
  const { pathname } = parse(request.url);

  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);

Client authentication

import WebSocket from 'ws';
import { createServer } from 'http';

const server = createServer();
const wss = new WebSocketServer({ noServer: true });

wss.on('connection', function connection(ws, request, client) {
  ws.on('message', function message(msg) {
    console.log(`Received message ${msg} from user ${client}`);
  });
});

server.on('upgrade', function upgrade(request, socket, head) {
  // This function is not defined on purpose. Implement it with your own logic.
  authenticate(request, (err, client) => {
    if (err || !client) {
      socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
      socket.destroy();
      return;
    }

    wss.handleUpgrade(request, socket, head, function done(ws) {
      wss.emit('connection', ws, request, client);
    });
  });
});

server.listen(8080);

Also see the provided example using express-session.

Server broadcast

A client WebSocket broadcasting to all connected WebSocket clients, including itself.

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(data, isBinary) {
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: isBinary });
      }
    });
  });
});

A client WebSocket broadcasting to every other connected WebSocket clients, excluding itself.

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(data, isBinary) {
    wss.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: isBinary });
      }
    });
  });
});

echo.websocket.org demo

import WebSocket from '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);
});

Use the Node.js streams API

import WebSocket, { createWebSocketStream } from 'ws';

const ws = new WebSocket('wss://echo.websocket.org/', {
  origin: 'https://websocket.org'
});

const duplex = createWebSocketStream(ws, { encoding: 'utf8' });

duplex.pipe(process.stdout);
process.stdin.pipe(duplex);

Other examples

For a full example with a browser client communicating with a ws server, see the examples folder.

Otherwise, see the test cases.

FAQ

How to get the IP address of the client?

The remote IP address can be obtained from the raw socket.

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws, req) {
  const ip = req.socket.remoteAddress;
});

When the server runs behind a proxy like NGINX, the de-facto standard is to use the X-Forwarded-For header.

wss.on('connection', function connection(ws, req) {
  const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
});

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.

import { WebSocketServer } from 'ws';

function heartbeat() {
  this.isAlive = true;
}

const wss = new WebSocketServer({ port: 8080 });

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();
  });
}, 30000);

wss.on('close', function close() {
  clearInterval(interval);
});

Pong messages are automatically sent in response to ping messages as required by the spec.

Just like the server example above your clients might as well lose connection without knowing it. You might want to add a ping listener on your clients to prevent that. A simple implementation would be:

import WebSocket from 'ws';

function heartbeat() {
  clearTimeout(this.pingTimeout);

  // Use `WebSocket#terminate()`, which immediately destroys the connection,
  // instead of `WebSocket#close()`, which waits for the close timer.
  // Delay should be equal to the interval at which your server
  // sends out pings plus a conservative assumption of the latency.
  this.pingTimeout = setTimeout(() => {
    this.terminate();
  }, 30000 + 1000);
}

const client = new WebSocket('wss://echo.websocket.org/');

client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
  clearTimeout(this.pingTimeout);
});

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 for changelog entries.

License

MIT

Comments
  • High memory usage (or memleak?)

    High memory usage (or memleak?)

    Hi all!

    I created a small example for the memory leak my ws (or WebSocket.IO) server has.

    This small server only accepts WebSocket connections and sends one message. Client count and memory usage is displayed every 2 seconds.

    The client open 10,000 connections, then every minute 2,000 new connections are openend and 2,000 are closed, so the count stays at about 10,000.

    https://gist.github.com/2152933

    Memory Graph

    The graph is the output of server.js (run on a EC2 "large" instance with node 0.6.13 and ws 0.4.9).

    • Server startup: RSS is about 13 MB (0 clients)
    • When the first 10,000 clients are connected, RSS = 420 MB
    • During the next 10 minutes clients come and go (see client.js), RSS grows to 620 MB
    • heap usage stay stable
    • Client is stopped =(> RSS falls to 220 MB waited 1 minute for GC)
    • After 1 minute, client ist started again => RSS jumps to 440 MB (10,000 clients)
    • During the next minutes, RSS grows again up to 630 MB (after 10 minutes)
    • Client is stopped => RSS falls to 495 MB
    • When I start the client again, RSS usage seems stable (at least compared to the two runs before).

    Questions:

    1. 400 MB for 10.000 clients (with no data attached) is much. But I see JS as an interpreted language is not that memory optimized than C. BUT, why does opening and closing 20.000 connections (during the 10 minute period) consume another 200 MB?
    2. The process is at about 30% CPU, so the GC has the chance to kick in (and, ws uses nextTick, so the GC really has a chance)
    3. Why is he GC unable to free the memory after the second run? Can't be the Buffer/SlowBuffer problem (fragmented small Buffers in different 8k SlowBuffers), as there are no Buffers used anymore...
    4. Why does the RSS usage remain pretty stable after the first two runs?

    The effect is the same (but slower) with only 1,000 client connections per minute, but things get even worse when I don't stop the client after 10 minutes. Our production server runs with about 30-40% CPU constantly (30k connections), 1-5% at night (1-2k connections), but is never completely idle. The growing of the RSS usage never seems to stop.

    On the production server, RSS grows until node crashes (0.4.x crashes at 1 GB) or the process gets killed by the system (0.6 supports more than 1 GB).

    I'll try two things tomorrow:

    • The same setup with Node 0.4 (as 0.4 seems much better in regards of memory consumption than 0.6), and
    • with different WebSocket libraries, e.g. "websock", which is not 100% stable but only consumes one third (still leaking though), and a variant of WebSocket.IO with the original Socket.IO (not ws!) hybi parsers, see my "nows" branch.

    The setup is a default Amazon EC2 large instance, so this must be an issue for anyone who runs a WebSocket server using ws (or must likely also WebSocket.IO with ws receivers) with some traffic. I refuse to believe Node is not capable of serving this.

    Or am I missing something?

    Nico

    opened by nicokaiser 107
  • Please delete wiki pages related to automatic reconnection.

    Please delete wiki pages related to automatic reconnection.

    Is there an existing issue for this?

    • [X] I've searched for any related issues and avoided creating a duplicate issue.

    Description

    • https://github.com/websockets/ws/wiki/client-auto-reconnect-with-ping-listener---exponential-back-off-timeout
    • https://github.com/websockets/ws/wiki/Websocket-client-implementation-for-auto-reconnect
    • https://github.com/websockets/ws/wiki/WebsocketClient-AutoReconnect-Working

    They are outdated and have poor code quality, especially the last one:

    • Reference to external variable URL
      this.instance = new WebSocket(URL, undefined, {
        handshakeTimeout: 10000
      });
      
    • autoReconnectInterval initialized twice:
      function WebSocketClient() {
        this.autoReconnectInterval = 5 * 1000; // ms
        this.pingTimeout = undefined;
        this.number = 0; // Message number
        this.autoReconnectInterval = 5 * 1000; // ms
      }
      

    ws version

    No response

    Node.js Version

    No response

    System

    No response

    Expected result

    No response

    Actual result

    No response

    Attachments

    No response

    opened by BlackGlory 85
  • read ECONNRESET error on disconnect (ws 3.3.3)

    read ECONNRESET error on disconnect (ws 3.3.3)

    • [x] I've searched for any related issues and avoided creating a duplicate issue.

    Description

    Upon updating to ws 3.3.3, when a client disconnects the following exception is thrown:

    events.js:183
          throw er; // Unhandled 'error' event
          ^
    
    Error: read ECONNRESET
        at _errnoException (util.js:1024:11)
        at TCP.onread (net.js:615:25)
    

    Code works fine in previous version.

    Reproducible in:

    version: 3.3.3 Node.js version(s): v8.9.1 OS version(s): Windows 10 Pro, Fall update

    Steps to reproduce:

    1. Create a websocket server
    2. Connect to the websocket server via Chrome (`new WebSocket('ws://localhost:3000'))
    3. Disconnect from the websocket server (via refreshing the page in Chrome)

    Expected result:

    The disconnect is handled gracefully, with the 'close' event being called.

    Actual result:

    An exception is thrown, which crashes the app:

    events.js:183
          throw er; // Unhandled 'error' event
          ^
    
    Error: read ECONNRESET
        at _errnoException (util.js:1024:11)
        at TCP.onread (net.js:615:25)
    
    opened by G-Rath 51
  • Connections stay in CLOSE_WAIT with Node 0.10.x

    Connections stay in CLOSE_WAIT with Node 0.10.x

    After upgrading to Node 0.10 my servers keep accumulating TCP connections in the CLOSE_WAIT state until it hits the max-files limit.

    Example server (based on the ws README example:

    var WebSocketServer = require('ws').Server
      , wss = new WebSocketServer({port: 8080});
    
    wss.on('connection', function(ws) {
      console.log('connection');
      ws.send('something');
      ws.on('close', function() {
        console.log('close');
        //ws.upgradeReq.socket.destroy();
      });
      ws.on('end', function() {
        console.log('end');
      });
    });
    
    • Run the above server
    • Open Chrome console and execute new WebSocket('ws://localhost:8080/'); several times (I could not reproduce this issue with wscat).
    • Hit reload (all the connections close, you see some "close" message in the server output
    • lsof | grep CLOSE_WAIT lists all the closed connections in CLOSE_WAIT state
    • Terminate the server
    • lsof | grep CLOSE_WAIT is empty

    If you modify the server and comment in the destroy line, there are no CLOSE_WAIT connections.

    opened by nicokaiser 35
  • Compile with warnings

    Compile with warnings

    Hi guys I have integrated the tool inside my project and I get this warning while compiling:

    This issue is coming from the https://github.com/zalmoxisus/remote-redux-devtools/issues/102, but is a WS library issue. Basically I notice that the BufferUtil.js check if the library is already present to use it, other use it implements it. The problem is that this will cause require to fire an warning.

    WARNING in ./node_modules/ws/lib/BufferUtil.js
    Module not found: Error: Can't resolve 'bufferutil' in '/Users/alban/projects/frontend-base/node_modules/ws/lib'
     @ ./node_modules/ws/lib/BufferUtil.js 35:21-42
     @ ./node_modules/ws/lib/Receiver.js
     @ ./node_modules/ws/index.js
     @ ./node_modules/socketcluster-client/lib/sctransport.js
     @ ./node_modules/socketcluster-client/lib/scsocket.js
     @ ./node_modules/socketcluster-client/index.js
     @ ./node_modules/remote-redux-devtools/lib/devTools.js
     @ ./node_modules/remote-redux-devtools/lib/index.js
     @ ./src/store/configureStore.js
     @ ./src/index.server.js
     @ multi ./src/index.server.js
    
    WARNING in ./node_modules/ws/lib/Validation.js
    Module not found: Error: Can't resolve 'utf-8-validate' in '/Users/alban/projects/frontend-base/node_modules/ws/lib'
     @ ./node_modules/ws/lib/Validation.js 10:22-47
     @ ./node_modules/ws/lib/Receiver.js
     @ ./node_modules/ws/index.js
     @ ./node_modules/socketcluster-client/lib/sctransport.js
     @ ./node_modules/socketcluster-client/lib/scsocket.js
     @ ./node_modules/socketcluster-client/index.js
     @ ./node_modules/remote-redux-devtools/lib/devTools.js
     @ ./node_modules/remote-redux-devtools/lib/index.js
     @ ./src/store/configureStore.js
     @ ./src/index.server.js
     @ multi ./src/index.server.js
    

    Wouldn't it be better to just include these libraries in the package.json or remove totally them from loading?

    opened by albandaft 34
  •  Cannot set property 'readyState' of undefined

    Cannot set property 'readyState' of undefined

    websocket.readyState = WebSocket.CLOSING; ^

    TypeError: Cannot set property 'readyState' of undefined at TLSSocket.socketOnClose (/node_modules/ws/lib/websocket.js:826:24) at TLSSocket.emit (events.js:187:15) at _handle.close (net.js:606:12) at Socket.done (_tls_wrap.js:386:7) at Object.onceWrapper (events.js:273:13) at Socket.emit (events.js:182:13) at TCP._handle.close (net.js:606:12)

    opened by mccoysc 33
  • KeepAlive implementation

    KeepAlive implementation

    Hi ! @nkzawa @humingchun @3rd-Eden As discussed in the #459 issue.

    I implement a Keep Alive protocol using websocket ping/pong. When the websocket don't reply to a ping with a pong, the serveur close the dead connection .

    As it say in the documentation you can active it with a function : websocket.setKeepAlive([enabled], [interval], [timeout])

    or as recommend @glennschler you can configure a server to enable it for all websockets: new ws.Server([options], [callback])

    • options Object
      • keepAlive Boolean|Object
        • interval Number
        • timeout Number

    This new feacture is documented and unit tested.

    Aside from that, I remain open to all suggestions.

    opened by FlorianBELLAZOUZ 32
  • wss: Error during WebSocket handshake: Unexpected response code: 200

    wss: Error during WebSocket handshake: Unexpected response code: 200

    Salutations,

    I'm running a WebSocket server using this module, but am having issues connecting with certain browsers. The server is served through ssl, so I connect to it using wss.

    The Issue

    When I connect to the website using Google Chrome, everything works as intended. However, when I try to connect using Chromium, I get the following error:

    WebSocket connection to 'wss://domain.name/' failed: Error during WebSocket handshake: Unexpected response code: 200
    

    It should be noted that the client connects perfectly fine on my test instance, connecting to ws://localhost:8085. This seems to be an issue only with wss protocols. Obviously, using ws protocols over an ssl connection is not a viable option.

    I have tried directly connecting via IP and port, but get the following error:

    WebSocket connection to 'wss://ip.addr.he.re:8190/' failed: Error in connection establishment: net::ERR_CONNECTION_CLOSED
    

    My Code

    Server:

    const WebSocketServer = require('ws').Server;
    const wss = new WebSocketServer({
        server: server // express server object, running on port 8085
      //port: 8190 // used when testing direct connections
    });
    

    Browser:

    var wss = new WebSocket("wss://domain.name");
    

    Sidenotes

    I suspect this issue is due to chromium's websocket support itself. I noticed that there is client support available. Is there a browserified version of this module available for me to load via a