This commit is contained in:
Ilya Kantor 2019-06-26 09:30:35 +03:00
parent 4f2a90854d
commit 1191dbb395
2 changed files with 34 additions and 31 deletions

View file

@ -71,9 +71,9 @@ Now let's talk more in-depth.
## Opening a websocket ## Opening a websocket
When `new WebSocket(url)` is created, it starts an HTTP handshake (HTTPS for `wss://`). When `new WebSocket(url)` is created, it starts connecting immediately.
The browser asks the server: "Do you support Websocket?" And if the server says "yes", then the talk continues in WebSocket protocol, which is not HTTP at all. 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.
![](websocket-handshake.png) ![](websocket-handshake.png)
@ -89,7 +89,7 @@ Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13 Sec-WebSocket-Version: 13
``` ```
- `Origin` -- the origin of the client page. WebSocket is cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compabitility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `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 compabitility issues. But `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. - `Connection: Upgrade` -- signals that the client would like to change the protocol.
- `Upgrade: websocket` -- the requested protocol is "websocket". - `Upgrade: websocket` -- the requested protocol is "websocket".
- `Sec-WebSocket-Key` -- a random browser-generated key for security. - `Sec-WebSocket-Key` -- a random browser-generated key for security.
@ -120,11 +120,11 @@ 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, not data itself.
- `Sec-WebSocket-Protocol: soap, wamp` means that we're going 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` is sent by the browser automatically, with a list of possible extensions it supports. `Sec-WebSocket-Extensions` header is sent by the browser automatically, with a list of possible extensions it supports.
`Sec-WebSocket-Protocol` depends on us: we decide what kind of data we send. The second optional parameter of `new WebSocket` lists subprotocols: `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 ```js
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]); let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
@ -161,24 +161,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 ## WebSocket data
WebSocket communication consists of "frames" that can be sent from either side: 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. - "connection close frame" and a few other service frames.
In the browser, we only care about text or binary frames. In the browser, we directly work only with text or binary frames.
**WebSocket `.send()` can send either text or binary data, doesn't matter.** **WebSocket `.send()` method can send either text or binary data.**
For sending, `socket.send(body)` allows strings or any 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.
**Textual data always comes as string. For receiving 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. The `socket.bufferType` is `"blob"` by default, so binary data comes in Blobs.
@ -193,16 +193,17 @@ socket.onmessage = (event) => {
## Rate limiting ## Rate limiting
Imagine, our app is generating a lot of data to send. But network connection is not that fast. The user may be on a mobile, in rural area. 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.
We can call `socket.send(data)` again and again. But the data will be buffered 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.
The `socket.bufferedAmount` property stores how many bytes are buffered at this moment, waiting to be sent over the network. The `socket.bufferedAmount` property stores how many bytes are 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. We can examine it to see whether the socket is actually available for transmission.
```js ```js
// every 100ms examine the socket and send more data only if no data buffered // every 100ms examine the socket and send more data
// only if all the existing data was sent out
setInterval(() => { setInterval(() => {
if (socket.bufferedAmount == 0) { if (socket.bufferedAmount == 0) {
socket.send(moreData()); socket.send(moreData());
@ -215,18 +216,21 @@ setInterval(() => {
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. 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 is: The method for that is:
```js ```js
socket.close([code], [reason]); socket.close([code], [reason]);
``` ```
Then the other party in `close` event handle can get the code and the reason, e.g.: - `code` is a special WebSocket closing code (optional)
- `reason` is a string that describes the reason of closing (optional)
Then the other party in `close` event handler gets the code and the reason, e.g.:
```js ```js
// one party: // closing party:
socket.close(1000, "Work complete"); socket.close(1000, "Work complete");
// another party // the other party
socket.onclose = event => { socket.onclose = event => {
// event.code === 1000 // event.code === 1000
// event.reason === "Work complete" // event.reason === "Work complete"
@ -234,12 +238,10 @@ socket.onclose = event => {
}; };
``` ```
The `code` is not just any number, but a special WebSocket closing code. Most common code values:
Most common values: - `1000` -- the default, normal closure (used if no `code` supplied),
- `1006` -- no way to such code manually, indicates that the connection was lost (no close frame).
- `1000` -- the default, normal closure,
- `1006` -- can't set such code manually, indicates that the connection was broken (no close frame).
There are other codes like: There are other codes like:
@ -316,9 +318,9 @@ Server-side code is a little bit beyond our scope here. We're using browser WebS
Still it can also be pretty simple. We'll use Node.js with <https://github.com/websockets/ws> module for websockets. Still it can also be pretty simple. We'll use Node.js with <https://github.com/websockets/ws> module for websockets.
The 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 listen for its messages. 2. For each accepted websocket, `clients.add(socket)` and add `onmessage` listener for 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)`.
@ -329,7 +331,8 @@ const wss = new ws.Server({noServer: true});
const clients = new Set(); const clients = new Set();
http.createServer((req, res) => { http.createServer((req, res) => {
// in real project we have additional code here to handle non-websocket requests // here we only handle websocket connections
// in real project we'd have some other code herre to handle non-websocket requests
wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect); wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect);
}); });
@ -378,8 +381,8 @@ Events:
- `error`, - `error`,
- `close`. - `close`.
WebSocket by itself does not include reconnection, authentication and many other high-level mechanisms. So there are client/server libraries that add them. But it's also possible to implement these manually and integrate WebSockets with an existing site. WebSocket by itself does not include reconnection, authentication and many other high-level mechanisms. So there are client/server libraries for that, and it's also possible to implement these capabilities manually.
For integration purposes, a WebSocket server is usually running in parallel with the main 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. Many servers (such as Node.js) can support both HTTP and WebSocket protocols.

View file

@ -111,7 +111,7 @@ Regexp "open HTML-tag without attributes", like `<span>` or `<p>`: `pattern:/<[a
alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body> alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>
``` ```
We look for character `pattern:'<'` followed by one or more English letters, and then `pattern:'>'`. We look for character `pattern:'<'` followed by one or more Latin letters, and then `pattern:'>'`.
Regexp "open HTML-tag without attributes" (improved): `pattern:/<[a-z][a-z0-9]*>/i` Regexp "open HTML-tag without attributes" (improved): `pattern:/<[a-z][a-z0-9]*>/i`
: Better regexp: according to the standard, HTML tag name may have a digit at any position except the first one, like `<h1>`. : Better regexp: according to the standard, HTML tag name may have a digit at any position except the first one, like `<h1>`.
@ -132,7 +132,7 @@ We can see one common rule in these examples: the more precise is the regular ex
For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`. For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`.
...But because `pattern:\w` means any English letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. So it's much simpler than `pattern:<[a-z][a-z0-9]*>`, but less reliable. ...But because `pattern:\w` means any Latin letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. So it's much simpler than `pattern:<[a-z][a-z0-9]*>`, but less reliable.
Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`? Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`?