This commit is contained in:
Ilya Kantor 2019-08-02 14:37:29 +03:00
parent f88a41b312
commit 0b23bfb177
3 changed files with 28 additions and 27 deletions

View file

@ -76,7 +76,7 @@ During the connection the browser (using headers) asks the server: "Do you suppo
![](websocket-handshake.svg) ![](websocket-handshake.svg)
Here's an example of browser request for `new WebSocket("wss://javascript.info/chat")`. Here's an example of browser headers for request made by `new WebSocket("wss://javascript.info/chat")`.
``` ```
GET /chat GET /chat
@ -117,17 +117,15 @@ There may be additional headers `Sec-WebSocket-Extensions` and `Sec-WebSocket-Pr
For instance: For instance:
- `Sec-WebSocket-Extensions: deflate-frame` means that the browser supports data compression. An extension is something related to transferring the data, not data itself. - `Sec-WebSocket-Extensions: deflate-frame` means that the browser supports data compression. An extension is something related to transferring the data, functionality that extends WebSocket protocol. The header `Sec-WebSocket-Extensions` is sent automatically by the browser, with the list of all extenions it supports.
- `Sec-WebSocket-Protocol: soap, wamp` means that we'd like to transfer not just any data, but the data in [SOAP](http://en.wikipedia.org/wiki/SOAP) or WAMP ("The WebSocket Application Messaging Protocol") protocols. WebSocket subprotocols are registered in the [IANA catalogue](http://www.iana.org/assignments/websocket/websocket.xml). - `Sec-WebSocket-Protocol: soap, wamp` means that we'd like to transfer not just any data, but the data in [SOAP](http://en.wikipedia.org/wiki/SOAP) or WAMP ("The WebSocket Application Messaging Protocol") protocols. WebSocket subprotocols are registered in the [IANA catalogue](http://www.iana.org/assignments/websocket/websocket.xml).
`Sec-WebSocket-Extensions` header is sent by the browser automatically, with a list of possible extensions it supports. This optional header is set by us, to tell the server which subprotocols our code supports, using the second (optional) parameter of `new WebSocket`. That's the array of subprotocols, e.g. if we'd like to use SOAP or WAMP:
`Sec-WebSocket-Protocol` header depends on us: we decide what kind of data we send. The second optional parameter of `new WebSocket` is just for that, it lists subprotocols: ```js
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
```js ```
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. The server should respond with a list of protocols and extensions that it agrees to use.
@ -162,24 +160,24 @@ Sec-WebSocket-Protocol: soap
Here the server responds that it supports the extension "deflate-frame", and only SOAP of the requested subprotocols. Here the server responds that it supports the extension "deflate-frame", and only SOAP of the requested subprotocols.
## WebSocket data ## Data transfer
WebSocket communication consists of "frames" -- data fragments, that can be sent from either side, and can be of several kinds: 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. - "text frames" -- contain text data that parties send to each other.
- "binary data frames" -- contain binary 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. - "ping/pong frames" are used to check the connection, sent from the server, the browser responds to these automatically.
- "connection close frame" and a few other service frames. - there's also "connection close frame" and a few other service frames.
In the browser, we directly work only with text or binary frames. In the browser, we directly work only with text or binary frames.
**WebSocket `.send()` method can send either text or binary data.** **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 required: just send it out. A call `socket.send(body)` allows `body` in string or a binary format, including `Blob`, `ArrayBuffer`, etc. No settings 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.** **When we receive the data, text always comes as string. And for binary data, we can choose between `Blob` and `ArrayBuffer` formats.**
The `socket.bufferType` is `"blob"` by default, so binary data comes in Blobs. That's set by `socket.bufferType` property, it's `"blob"` by default, so binary data comes as `Blob` objects.
[Blob](info:blob) is a high-level binary object, it directly integrates with `<a>`, `<img>` and other tags, so that's a sane default. But for binary processing, to access individual data bytes, we can change it to `"arraybuffer"`: [Blob](info:blob) is a high-level binary object, it directly integrates with `<a>`, `<img>` and other tags, so that's a sane default. But for binary processing, to access individual data bytes, we can change it to `"arraybuffer"`:
@ -192,7 +190,7 @@ socket.onmessage = (event) => {
## Rate limiting ## 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, outside of a city. 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. 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.
@ -249,7 +247,7 @@ There are other codes like:
- `1011` -- unexpected error on server, - `1011` -- unexpected error on server,
- ...and so on. - ...and so on.
Please refer to the [RFC6455, §7.4.1](https://tools.ietf.org/html/rfc6455#section-7.4.1) for the full list. The full list can be found in [RFC6455, §7.4.1](https://tools.ietf.org/html/rfc6455#section-7.4.1).
WebSocket codes are somewhat like HTTP codes, but different. In particular, any codes less than `1000` are reserved, there'll be an error if we try to set such a code. WebSocket codes are somewhat like HTTP codes, but different. In particular, any codes less than `1000` are reserved, there'll be an error if we try to set such a code.
@ -275,9 +273,9 @@ To get connection state, additionally there's `socket.readyState` property with
## Chat example ## Chat example
Let's review a chat example using browser WebSocket API and Node.js WebSocket module <https://github.com/websockets/ws>. 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: there's a `<form>` to send messages and a `<div>` for incoming messages: HTML: we need a `<form>` to send messages and a `<div>` for incoming messages:
```html ```html
<!-- message form --> <!-- message form -->
@ -290,7 +288,12 @@ HTML: there's a `<form>` to send messages and a `<div>` for incoming messages:
<div id="messages"></div> <div id="messages"></div>
``` ```
JavaScript is also simple. We open a socket, then on form submission -- `socket.send(message)`, on incoming message -- append it to `div#messages`: From JavaScript we want three things:
1. Open the connection.
2. On form submission -- `socket.send(message)` for the message.
3. On incoming message -- append it to `div#messages`.
Here's the code:
```js ```js
let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws"); let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws");
@ -303,7 +306,7 @@ document.forms.publish.onsubmit = function() {
return false; return false;
}; };
// show message in div#messages // message received - show the message in div#messages
socket.onmessage = function(event) { socket.onmessage = function(event) {
let message = event.data; let message = event.data;
@ -313,13 +316,12 @@ socket.onmessage = function(event) {
} }
``` ```
Server-side code is a little bit beyond our scope here. We're using browser WebSocket API, a server may have another library. Server-side code is a little bit beyond our scope. Here we'll use Node.js, but you don't have to. Other platforms also have their means to work with WebSocket.
Still it can also be pretty simple. We'll use Node.js with <https://github.com/websockets/ws> module for websockets.
The server-side algorithm will be: The server-side algorithm will be:
1. Create `clients = new Set()` -- a set of sockets. 1. Create `clients = new Set()` -- a set of sockets.
2. For each accepted websocket, `clients.add(socket)` and add `message` event listener for its messages. 2. For each accepted websocket, add it to the set `clients.add(socket)` and setup `message` event listener to get its messages.
3. When a message received: iterate over clients and send it to everyone. 3. When a message received: iterate over clients and send it to everyone.
4. When a connection is closed: `clients.delete(socket)`. 4. When a connection is closed: `clients.delete(socket)`.
@ -359,7 +361,6 @@ Here's the working example:
You can also download it (upper-right button in the iframe) and run locally. Just don't forget to install [Node.js](https://nodejs.org/en/) and `npm install ws` before running. You can also download it (upper-right button in the iframe) and run locally. Just don't forget to install [Node.js](https://nodejs.org/en/) and `npm install ws` before running.
## Summary ## Summary
WebSocket is a modern way to have persistent browser-server connections. WebSocket is a modern way to have persistent browser-server connections.
@ -384,4 +385,4 @@ WebSocket by itself does not include reconnection, authentication and many other
Sometimes, to integrate WebSocket into existing project, people run WebSocket server in parallel with the main HTTP-server, and they share a single database. Requests to WebSocket use `wss://ws.site.com`, a subdomain that leads to WebSocket server, while `https://site.com` goes to the main HTTP-server. Sometimes, to integrate WebSocket into existing project, people run WebSocket server in parallel with the main HTTP-server, and they share a single database. Requests to WebSocket use `wss://ws.site.com`, a subdomain that leads to WebSocket server, while `https://site.com` goes to the main HTTP-server.
Surely, other ways of integration are also possible. Many servers (such as Node.js) can support both HTTP and WebSocket protocols. Surely, other ways of integration are also possible.

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="429px" height="404px" viewBox="0 0 429 404" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="429px" height="348px" viewBox="0 0 429 348" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 55.2 (78181) - https://sketchapp.com --> <!-- Generator: sketchtool 55.2 (78181) - https://sketchapp.com -->
<title>websocket-handshake.svg</title> <title>websocket-handshake.svg</title>
<desc>Created with sketchtool.</desc> <desc>Created with sketchtool.</desc>
@ -18,7 +18,7 @@
<path id="Line" d="M349,141 L68,141 L68,139 L349,139 L349,133 L363,140 L349,147 L349,141 Z" fill="#EE6B47" fill-rule="nonzero"></path> <path id="Line" d="M349,141 L68,141 L68,139 L349,139 L349,133 L363,140 L349,147 L349,141 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<path id="Line-Copy" d="M83,210 L364,210 L364,212 L83,212 L83,218 L69,211 L83,204 L83,210 Z" fill="#EE6B47" fill-rule="nonzero"></path> <path id="Line-Copy" d="M83,210 L364,210 L364,212 L83,212 L83,218 L69,211 L83,204 L83,210 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<text id="HTTP-request" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" line-spacing="22" fill="#8A704D"> <text id="HTTP-request" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" line-spacing="22" fill="#8A704D">
<tspan x="171.515137" y="130">HTTP-request</tspan> <tspan x="172.015137" y="130">HTTP-request</tspan>
</text> </text>
<text id="&quot;Hey,-server,-let's" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" line-spacing="22" fill="#8A704D"> <text id="&quot;Hey,-server,-let's" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" line-spacing="22" fill="#8A704D">
<tspan x="103.374512" y="161">"Hey, server, let's talk WebSocket?"</tspan> <tspan x="103.374512" y="161">"Hey, server, let's talk WebSocket?"</tspan>
@ -29,7 +29,7 @@
</text> </text>
<path id="Line-Copy-2" d="M81,278 L83,278 L83,280 L81,280 L81,286 L67,279 L81,272 L81,278 Z M363,279 L349,286 L349,272 L363,279 Z M85,280 L85,278 L89,278 L89,280 L85,280 Z M91,280 L91,278 L95,278 L95,280 L91,280 Z M97,280 L97,278 L101,278 L101,280 L97,280 Z M103,280 L103,278 L107,278 L107,280 L103,280 Z M109,280 L109,278 L113,278 L113,280 L109,280 Z M115,280 L115,278 L119,278 L119,280 L115,280 Z M121,280 L121,278 L125,278 L125,280 L121,280 Z M127,280 L127,278 L131,278 L131,280 L127,280 Z M133,280 L133,278 L137,278 L137,280 L133,280 Z M139,280 L139,278 L143,278 L143,280 L139,280 Z M145,280 L145,278 L149,278 L149,280 L145,280 Z M151,280 L151,278 L155,278 L155,280 L151,280 Z M157,280 L157,278 L161,278 L161,280 L157,280 Z M163,280 L163,278 L167,278 L167,280 L163,280 Z M169,280 L169,278 L173,278 L173,280 L169,280 Z M175,280 L175,278 L179,278 L179,280 L175,280 Z M181,280 L181,278 L185,278 L185,280 L181,280 Z M187,280 L187,278 L191,278 L191,280 L187,280 Z M193,280 L193,278 L197,278 L197,280 L193,280 Z M199,280 L199,278 L203,278 L203,280 L199,280 Z M205,280 L205,278 L209,278 L209,280 L205,280 Z M211,280 L211,278 L215,278 L215,280 L211,280 Z M217,280 L217,278 L221,278 L221,280 L217,280 Z M223,280 L223,278 L227,278 L227,280 L223,280 Z M229,280 L229,278 L233,278 L233,280 L229,280 Z M235,280 L235,278 L239,278 L239,280 L235,280 Z M241,280 L241,278 L245,278 L245,280 L241,280 Z M247,280 L247,278 L251,278 L251,280 L247,280 Z M253,280 L253,278 L257,278 L257,280 L253,280 Z M259,280 L259,278 L263,278 L263,280 L259,280 Z M265,280 L265,278 L269,278 L269,280 L265,280 Z M271,280 L271,278 L275,278 L275,280 L271,280 Z M277,280 L277,278 L281,278 L281,280 L277,280 Z M283,280 L283,278 L287,278 L287,280 L283,280 Z M289,280 L289,278 L293,278 L293,280 L289,280 Z M295,280 L295,278 L299,278 L299,280 L295,280 Z M301,280 L301,278 L305,278 L305,280 L301,280 Z M307,280 L307,278 L311,278 L311,280 L307,280 Z M313,280 L313,278 L317,278 L317,280 L313,280 Z M319,280 L319,278 L323,278 L323,280 L319,280 Z M325,280 L325,278 L329,278 L329,280 L325,280 Z M331,280 L331,278 L335,278 L335,280 L331,280 Z M337,280 L337,278 L341,278 L341,280 L337,280 Z M343,280 L343,278 L347,278 L347,280 L343,280 Z" fill="#EE6B47" fill-rule="nonzero"></path> <path id="Line-Copy-2" d="M81,278 L83,278 L83,280 L81,280 L81,286 L67,279 L81,272 L81,278 Z M363,279 L349,286 L349,272 L363,279 Z M85,280 L85,278 L89,278 L89,280 L85,280 Z M91,280 L91,278 L95,278 L95,280 L91,280 Z M97,280 L97,278 L101,278 L101,280 L97,280 Z M103,280 L103,278 L107,278 L107,280 L103,280 Z M109,280 L109,278 L113,278 L113,280 L109,280 Z M115,280 L115,278 L119,278 L119,280 L115,280 Z M121,280 L121,278 L125,278 L125,280 L121,280 Z M127,280 L127,278 L131,278 L131,280 L127,280 Z M133,280 L133,278 L137,278 L137,280 L133,280 Z M139,280 L139,278 L143,278 L143,280 L139,280 Z M145,280 L145,278 L149,278 L149,280 L145,280 Z M151,280 L151,278 L155,278 L155,280 L151,280 Z M157,280 L157,278 L161,278 L161,280 L157,280 Z M163,280 L163,278 L167,278 L167,280 L163,280 Z M169,280 L169,278 L173,278 L173,280 L169,280 Z M175,280 L175,278 L179,278 L179,280 L175,280 Z M181,280 L181,278 L185,278 L185,280 L181,280 Z M187,280 L187,278 L191,278 L191,280 L187,280 Z M193,280 L193,278 L197,278 L197,280 L193,280 Z M199,280 L199,278 L203,278 L203,280 L199,280 Z M205,280 L205,278 L209,278 L209,280 L205,280 Z M211,280 L211,278 L215,278 L215,280 L211,280 Z M217,280 L217,278 L221,278 L221,280 L217,280 Z M223,280 L223,278 L227,278 L227,280 L223,280 Z M229,280 L229,278 L233,278 L233,280 L229,280 Z M235,280 L235,278 L239,278 L239,280 L235,280 Z M241,280 L241,278 L245,278 L245,280 L241,280 Z M247,280 L247,278 L251,278 L251,280 L247,280 Z M253,280 L253,278 L257,278 L257,280 L253,280 Z M259,280 L259,278 L263,278 L263,280 L259,280 Z M265,280 L265,278 L269,278 L269,280 L265,280 Z M271,280 L271,278 L275,278 L275,280 L271,280 Z M277,280 L277,278 L281,278 L281,280 L277,280 Z M283,280 L283,278 L287,278 L287,280 L283,280 Z M289,280 L289,278 L293,278 L293,280 L289,280 Z M295,280 L295,278 L299,278 L299,280 L295,280 Z M301,280 L301,278 L305,278 L305,280 L301,280 Z M307,280 L307,278 L311,278 L311,280 L307,280 Z M313,280 L313,278 L317,278 L317,280 L313,280 Z M319,280 L319,278 L323,278 L323,280 L319,280 Z M325,280 L325,278 L329,278 L329,280 L325,280 Z M331,280 L331,278 L335,278 L335,280 L331,280 Z M337,280 L337,278 L341,278 L341,280 L337,280 Z M343,280 L343,278 L347,278 L347,280 L343,280 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<text id="WebSocket-protocol" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" fill="#8A704D"> <text id="WebSocket-protocol" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal" fill="#8A704D">
<tspan x="151.524414" y="272">WebSocket protocol</tspan> <tspan x="152.024414" y="272">WebSocket protocol</tspan>
</text> </text>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

Binary file not shown.