WebSocket
The WebSocket protocol, described in the specification RFC 6455, provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as “packets”, without breaking the connection and the need of additional HTTP-requests. WebSocket is especially great for services that require continuous data exchange, e.g. online games, real-time trading systems and so on.
A simple example
To open a websocket connection, we need to create new WebSocket using the special protocol ws in the url: There’s also encrypted wss:// protocol. It’s like HTTPS for websockets. Once the socket is created, we should listen to events on it. There are totally 4 events: - open – connection established, - message – data received, - error – websocket error, - close – connection closed. …And if we’d like to send something, then socket.send(data) will do that. Here’s an example: For demo purposes, there’s a small server server.js written in Node.js, for the example above, running. It responds with “Hello from server, John”, then waits 5 seconds and closes the connection. So you’ll see events open -> message -> close. That’s actually it, we can talk WebSocket already. Quite simple, isn’t it? Now let’s talk more in-depth.
Opening a websocket
When new WebSocket(url) is created, it starts connecting immediately. During the connection, the browser (using headers) asks the server: “Do you support Websocket?” And if the server replies “yes”, then the talk continues in WebSocket protocol, which is not HTTP at all. Here’s an example of browser headers for a request made by new WebSocket(“wss://javascript.info/chat”). - Origin – the origin of the client page, e.g. https://javascript.info. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But the Origin header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - Connection: Upgrade – signals that the client would like to change the protocol. - Upgrade: websocket – the requested protocol is “websocket”. - Sec-WebSocket-Key – a random browser-generated key, used to ensure that the server supports WebSocket protocol. It’s random to prevent proxies from caching any following communication. - Sec-WebSocket-Version – WebSocket protocol version, 13 is the current one. If the server agrees to switch to WebSocket, it should send code 101 response: Here Sec-WebSocket-Accept is Sec-WebSocket-Key, recoded using a special algorithm. Upon seeing it, the browser understands that the server really does support the WebSocket protocol. Afterwards, the data is transferred using the WebSocket protocol, we’ll see its structure (“frames”) soon. And that’s not HTTP at all.
Extensions and subprotocols
There may be additional headers Sec-WebSocket-Extensions and Sec-WebSocket-Protocol that describe extensions and subprotocols. For instance: - Sec-WebSocket-Extensions: deflate-frame means that the browser supports data compression. An extension is something related to transferring the data, functionality that extends the WebSocket protocol. The header Sec-WebSocket-Extensions is sent automatically by the browser, with the list of all extensions it supports. - Sec-WebSocket-Protocol: soap, wamp means that we’d like to transfer not just any data, but the data in SOAP or WAMP (“The WebSocket Application Messaging Protocol”) protocols. WebSocket subprotocols are registered in the IANA catalogue. So, this header describes the data formats that we’re going to use. This optional header is set using the second parameter of new WebSocket. That’s the array of subprotocols, e.g. if we’d like to use SOAP or WAMP:
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
The server should respond with a list of protocols and extensions that it agrees to use. For example, the request: Response: Here the server responds that it supports the extension “deflate-frame”, and only SOAP of the requested subprotocols.
Data transfer
WebSocket communication consists of “frames” – data fragments, that can be sent from either side, and can be of several kinds:
- “text frames” – contain text data that parties send to each other.
- “binary data frames” – contain binary data that parties send to each other.
- “ping/pong frames” are used to check the connection, sent from the server, the browser responds to these automatically.
- there’s also “connection close frame” and a few other service frames.
In the browser, we directly work only with text or binary frames.
WebSocket .send() method can send either text or binary data.
A call socket.send(body) allows body in string or a binary format, including Blob, ArrayBuffer, etc. No settings are required: just send it out in any format.
When we receive the data, text always comes as string. And for binary data, we can choose between Blob and ArrayBuffer formats.
That’s set by socket.binaryType property, it’s “blob” by default, so binary data comes as Blob objects.
Blob is a high-level binary object, it directly integrates with , and other tags, so that’s a sane default. But for binary processing, to access individual data bytes, we can change it to “arraybuffer”:
Rate limiting
Imagine, our app is generating a lot of data to send. But the user has a slow network connection, maybe on a mobile internet, outside of a city. We can call socket.send(data) again and again. But the data will be buffered (stored) in memory and sent out only as fast as network speed allows. The socket.bufferedAmount property stores how many bytes remain buffered at this moment, waiting to be sent over the network. We can examine it to see whether the socket is actually available for transmission.
Connection close
Normally, when a party wants to close the connection (both browser and server have equal rights), they send a “connection close frame” with a numeric code and a textual reason. The method for that is: - code is a special WebSocket closing code (optional) - reason is a string that describes the reason of closing (optional) Then the other party in the close event handler gets the code and the reason, e.g.: Most common code values: - 1000 – the default, normal closure (used if no code supplied), - 1006 – no way to set such code manually, indicates that the connection was lost (no close frame). There are other codes like: - 1001 – the party is going away, e.g. server is shutting down, or a browser leaves the page, - 1009 – the message is too big to process, - 1011 – unexpected error on server, - …and so on. The full list can be found in RFC6455, §7.4.1. WebSocket codes are somewhat like HTTP codes, but different. In particular, codes lower than 1000 are reserved, there’ll be an error if we try to set such a code.
Connection state
To get connection state, additionally there’s socket.readyState property with values: - 0 – “CONNECTING”: the connection has not yet been established, - 1 – “OPEN”: communicating, - 2 – “CLOSING”: the connection is closing, - 3 – “CLOSED”: the connection is closed.
Chat example
Let’s review a chat example using browser WebSocket API and Node.js WebSocket module https://github.com/websockets/ws. We’ll pay the main attention to the client side, but the server is also simple. HTML: we need a