Overview
WebSocket is an event-driven, full-duplex asynchronous communications channel for your web applications. It has the ability to give you real-time updates that in the past you would use long polling or other hacks to achieve. The primary benefit is reducing resource needs on both the client and (more important) the server.
While WebSocket uses HTTP as the initial transport mechanism, the communication doesn’t end after a response is received by the client. Using the WebSocket API, you can be freed from the constraints of the typical HTTP request/response cycle. This also means that as long as the connection stays open, the client and server can freely send messages asynchronously without polling for anything new.
Initializing
The constructor for WebSocket requires a URL in order to initiate a connection to the server. By default, if no port is specified after the host, it will connect via port 80
(the HTTP port) or port 443
(the HTTPS port).
For now, because you’ll be running the WebSocket server locally, without a web server proxying the connection, you can simply initialize the browser’s native Web‐Socket object with the following code:
You now have a WebSocket object called ws that you can use to listen for events. The section “WebSocket Events” on page 12 details various events available to listen for. Table 2-1 lists the constructor parameters available with WebSocket.
Table 2-1. WebSocket Constructor Parameters
Parameter Name | Description |
---|---|
URL |
ws:// or wss:// (if using TLS) |
protocol (optional) |
Parameter specifying subprotocols that may be used as an array or single string |
WebSocket Events
The API for WebSocket is based around events
. This section covers the four events that your stock-ticker code can listen for.
WebSocket fires four events, which are available from the JavaScript API and defined by the W3C:
- open
- message
- error
- close
With JavaScript, you listen for these events to fire either with the handler on <eventname>
, or the addEventListener()
method. Your code will provide a callback that will execute every time that event gets fired.
Event: Open
When the WebSocket server responds to the connection request, and the handshake is complete, the open
event fires and the connection is established. Once this happens, the server has completed the handshake and is ready to send and receive messages from your client application:
// WebSocket connection established
ws.onopen = function (e) {
console.log("Connection established");
ws.send(JSON.stringify(stock_request));
};
From within this handler you can send messages to the server and output the status to the screen, and the connection is ready and available for bidirectional communication. The initial message being sent to the server over WebSocket is the stock_request
structure as a JSON string. Your server now knows what stocks you want to get updates on and will send them back to the client in one-second intervals.
Event: Message
After you’ve established a connection to the WebSocket server, it will be available to send messages to (you’ll look at that in “WebSocket Methods” on page 16
), and receive messages. The WebSocket API will prepare complete messages to be processed in the onmessage
handler.
// UI update function
var changeStockEntry = function (symbol, originalValue, newValue) {
var valElem = $("#" + symbol + " span");
valElem.html(newValue.toFixed(2));
if (newValue < originalValue) {
valElem.addClass("label-danger");
valElem.removeClass("label-success");
} else if (newValue > originalValue) {
valElem.addClass("label-success");
valElem.removeClass("label-danger");
}
};
// WebSocket message handler
ws.onmessage = function (e) {
var stocksData = JSON.parse(e.data);
for (var symbol in stocksData) {
if (stocksData.hasOwnProperty(symbol)) {
changeStockEntry(symbol, stocks[symbol], stocksData[symbol]);
stocks[symbol] = stocksData[symbol];
}
}
};
You can see from this short snippet that the handler is receiving a message from the server via an onmessage
callback. When querying for data, the data attribute will contain updated stock values. The preceding code snippet does the following:
- Parses the JSON response within
e.data
- Iterates over the associative array
- Ensures that the key exists in the array
- Calls your UI update fragment
- Assigns the new stock values to your local array
You’re passing around regular strings here, but WebSocket has full support for sending text and binary data.
Event: Error
When a failure happens for any reason at all, the handler you’ve attached to the error
event gets fired. When an error occurs, it can be assumed that the WebSocket connection will close and a close event will fire. Because the close event happens shortly after an error in some instances, the code and reason attributes can give you some indication as to what happened. Here’s a sample of how to handle the error case, and possibly reconnect to the WebSocket server as well:
Event: PING/PONG
The WebSocket protocol calls out two frame types:
PING
&PONG
.
The WebSocket JavaScript client API provides no capability to send a PING
frame to the server. PING
frames are sent out by the server only, and browser implementations should send back PONG frames in response.
Event: Close
The close event fires when the WebSocket connection closes, and the callback onerror will be executed. You can manually trigger calling the onclose
event by executing the close()
method on a WebSocket object, which will terminate the connection with the server. Once the connection is closed, communication between client and server will not continue. The following example zeros out the stocks array upon a close event being fired to show cleaning up resources:
ws.onclose = function (e) {
console.log(e.reason + " " + e.code);
for (var symbol in stocks) {
if (stocks.hasOwnProperty(symbol)) {
stocks[symbol] = 0;
}
}
};
ws.close(1000, "WebSocket connection closed");
As mentioned briefly in “Event: Error” on page 15
, two attributes, code and reason, are conveyed by the server and could indicate an error condition to be handled and/or a reason for the close event (other than normal expectation). Either side may terminate the connection via the close() method on the WebSocket object, as shown in the preceding code. Your code can also use the boolean attribute wasClean to find out if the termination was clean, or to see the result of an error state.
The readyState
value will move from closing (2) to closed (3). Now let’s move on to the methods available to your WebSocket object.
WebSocket Methods
The creators of WebSocket kept its methods pretty simple—there are only two:
send()
&close()
.
Method: Send
When your connection has been established, you’re ready to start sending (and receiving) messages to/from the WebSocket server. The client application can specify what type of data is being passed in and will accept several, including string and binary values. As shown earlier, the client code is sending a JSON string of listed stocks:
Of course, performing this send just anywhere won’t be appropriate. As we’ve discussed, WebSocket is event-driven, so you need to ensure that the connection is open and ready to receive messages. You can achieve this in two main ways.
You can perform your send from within the onopen
event:
var ws = new WebSocket("ws://localhost:8181");
ws.onopen = function (e) {
ws.send(JSON.stringify(stock_request));
};
Or you can check the readyState
attribute to ensure that the WebSocket object is ready to receive messages:
function processEvent(e) {
if (ws.readyState === WebSocket.OPEN) {
// Socket open, send!
ws.send(e);
} else {
// Show an error, queue it for sending later, etc
}
}
Method: Close
You close the WebSocket connection or terminate an attempt at connection is done via the close()
method. After this method is called, no more data can be sent or received from this connection. And calling it multiple times has no effect.
Here’s an example of calling the close() method without arguments:
Optionally, you can pass a numeric code and a human-readable reason through the close()
method. This gives some indication to the server as to why the connection was closed on the client end. The following code shows how to pass those values.
Note that if you don’t pass a code, the status 1000 is assumed, which means CLOSE_NORMAL:
Table 2-2 lists the status codes you can use in the WebSocket close()
method.
Table 2-2. WebSocket Close Codes
Status Code | Name | Description |
---|---|---|
0–999 | — | Reserved and not used. |
1000 | CLOSE_NORMAL |
Normal closure; the connection successfully completed. |
1001 | CLOSE_GOING_AWAY |
The endpoint is going away, either because of a server failure or because the browser is navigating away. |
1002 | CLOSE_PROTOCOL_ERROR |
The endpoint is terminating the connection due to a protocol error. |
1003 | CLOSE_UNSUPPORTED |
The connection is being terminated because the endpoint received data of a type it cannot accept. |
1004 | CLOSE_TOO_LARGE |
The endpoint is terminating the connection because a data frame was received that is too large. |
1005 | CLOSE_NO_STATUS |
Reserved. Indicates that no status code was provided even though one was expected. |
1006 | CLOSE_ABNORMAL |
Reserved. Used to indicate that a connection was closed abnormally. |
1007–1999 | — | Reserved for future use by the WebSocket standard. |
2000–2999 | — | Reserved for use by WebSocket extensions. |
3000–3999 | — | Available for use by libraries and frameworks. May not be used by applications. |
4000–4999 | — | Available for use by applications. |
WebSocket Attributes
When the event for open
is fired, the WebSocket object can have several possible attributes that can be read in your client applications. This section presents the attributes and the best practices for using them in your client code.
Attribute: readyState
The state of the WebSocket connection can be checked via the read-only WebSocket object attribute readyState
. The value of readyState
will change, and it is a good idea to check it before committing to send any data to the server.
Attribute: bufferedAmount
Attribute: protocol
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({ port: 8181 });
var stocks = {
AAPL: 95.0,
MSFT: 50.0,
AMZN: 300.0,
GOOG: 550.0,
YHOO: 35.0,
};
function randomInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
var stockUpdater;
var randomStockUpdater = function () {
for (var symbol in stocks) {
if (stocks.hasOwnProperty(symbol)) {
var randomizedChange = randomInterval(-150, 150);
var floatChange = randomizedChange / 100;
stocks[symbol] += floatChange;
}
}
var randomMSTime = randomInterval(500, 2500);
stockUpdater = setTimeout(function () {
randomStockUpdater();
}, randomMSTime);
};
randomStockUpdater();
wss.on("connection", function (ws) {
var clientStockUpdater;
var sendStockUpdates = function (ws) {
if (ws.readyState == 1) {
var stocksObj = {};
for (var i = 0; i < clientStocks.length; i++) {
symbol = clientStocks[i];
stocksObj[symbol] = stocks[symbol];
}
ws.send(JSON.stringify(stocksObj));
}
};
clientStockUpdater = setInterval(function () {
sendStockUpdates(ws);
}, 1000);
var clientStocks = [];
ws.on("message", function (message) {
var stock_request = JSON.parse(message);
clientStocks = stock_request["stocks"];
sendStockUpdates(ws);
});
ws.on("close", function () {
if (typeof clientStockUpdater !== "undefined") {
clearInterval(clientStockUpdater);
}
});
});
Testing for WebSocket Support
If you’ve coded anything for the Web over the years, it should come as no surprise that browsers do not always have support for the latest technology. Because some older browsers don’t support the WebSocket API, it is important to check for compatibility before using it.
Chapter 5
presents alternatives if the client browsers used by your community of users don’t support the WebSocket API. For now, here is a quick way to check whether the API is supported on the client:
if (window.WebSocket) {
console.log("WebSocket: supported");
// ... code here for doing WebSocket stuff
} else {
console.log("WebSocket: unsupported");
// ... fallback mode, or error back to user
}