This is the 27th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Writing in the front

The server pushes data to the client, and there are many solutions. In addition to “polling” and WebSocket, HTML 5 also provides Server-sent Events (SSE).

Generally speaking, the HTTP protocol can only be initiated by the client to the server, but the server cannot actively push requests to the client. However, there is a special case where the server declares to the client that the next message to be sent is streaming. That is, instead of sending a one-time packet, a stream of data is sent continuously. Instead of closing the connection, the client waits for a new data stream from the server. In essence, this communication is a stream of information that takes a long time to download.

SSE uses this mechanism to push information to the browser using streaming information. It is based on THE HTTP protocol and is currently supported by all browsers except IE/Edge.

Comparison with WebSocket

SSE is similar to WebSocket in that it establishes a communication channel between the browser and the server, and then the server pushes information to the browser.

Overall, WebSocket is more powerful and flexible. Because it is a full-duplex channel, two-way communication; SSE is a one-way channel that can only be sent from the server to the browser because streaming is essentially a download. If the browser sends a message to the server, it becomes another HTTP request.

However, SSE also has its advantages.

  • SSE uses HTTP protocol, which is supported by all existing server software. WebSocket is a standalone protocol.
  • SSE is lightweight and easy to use; The WebSocket protocol is relatively complex.
  • SSE supports disconnection and reconnection by default. WebSocket needs to implement disconnection and reconnection by itself.
  • SSE is generally used to transmit text only. Binary data needs to be encoded and transmitted. WebSocket supports binary data transmission by default.
  • SSE supports custom message types to be sent.

Therefore, both have their own characteristics and are suitable for different occasions.

The client API

The EventSource object

SSE’s client API is deployed on the EventSource object. The following code checks if the browser supports SSE.

if ('EventSource' in window) {
  // ...
}
Copy the code

With SSE, the browser first generates an EventSource instance to initiate a connection to the server.

var source = new EventSource(url);
Copy the code

The above URL can be co-domain with the current url or cross-domain. When you cross domains, you can specify a second parameter, which turns on the withCredentials property, indicating whether cookies are sent together.

var source = new EventSource(url, { withCredentials: true });
Copy the code

ReadyState attribute

The readyState property of the EventSource instance, indicating the current state of the connection. This property is read-only and can take the following values.

  • 0: equivalent to a constantEventSource.CONNECTING“Indicates that the connection is not established or is being reconnected.
  • 1: equivalent to a constantEventSource.OPEN, indicating that the connection has been established and data can be accepted.
  • 2: equivalent to a constantEventSource.CLOSED, indicating that the connection is disconnected and will not be reconnected.
var source = new EventSource(url);
console.log(source.readyState);
Copy the code

The url attribute

The URL property of the EventSource instance returns the url of the connection, which is read-only.

WithCredentials attribute

The withCredentials attribute of an EventSource instance returns a Boolean value indicating whether the withCredentials of CORS are enabled for the current instance. This property is read-only and defaults to false.

Onopen properties

Once the connection is established, the open event is triggered and the callback function can be defined in the onOpen property.

source.onopen = function (event) { // ... }; Source.addeventlistener ('open', function (event) {//... }, false);Copy the code

Onmessage property

When the client receives data from the server, it fires the Message event, and you can define a callback function in the onMessage property.

source.onmessage = function (event) { var data = event.data; var origin = event.origin; var lastEventId = event.lastEventId; // handle message }; Source. addEventListener('message', function (event) {var data = event. var origin = event.origin; var lastEventId = event.lastEventId; // handle message }, false);Copy the code

In the code above, the parameter object Event has the following properties.

  • data: Data returned by the server (text format).
  • origin: The domain part of the server URL, that is, the protocol, domain name, and port, indicates the source of the message.
  • lastEventId: Indicates the data number sent by the server. If there is no number, this property is null.

Onerror properties

If a communication error occurs (such as a broken connection), an error event is raised and a callback function can be defined in the onError property.

source.onerror = function (event) { // handle error event }; Source. addEventListener('error', function (event) {// handle error event}, false);Copy the code

Custom events

By default, data sent from the server always triggers a message event for the browser EventSource instance. Developers can also customize SSE events, in which sent back data does not trigger a Message event.

source.addEventListener('foo', function (event) {
  var data = event.data;
  var origin = event.origin;
  var lastEventId = event.lastEventId;
  // handle message
}, false);
Copy the code

In the code above, the browser listens for the event foo on SSE. See below to see how the server sends the foo event.

Close () method

The close method is used to close the SSE connection.

source.close();
Copy the code

Server implementation

The data format

The SSE data sent by the server to the browser must be UTF-8 encoded text with the following HTTP headers.

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Copy the code

In the first of the three lines, the content-type must specify the MIME Type as event-Steam.

A message sent each time consists of several messages separated by \n\n. Each message is internally composed of several lines, each in the following format.

[field]: value\n
Copy the code

The field above can take four values.

  • data
  • event
  • id
  • retry

In addition, you can have lines starting with colons to indicate comments. Typically, the server sends a comment to the browser every once in a while to keep the connection going.

: This is a comment
Copy the code

Here’s an example.

: this is a test stream\n\n

data: some text\n\n

data: another message\n
data: with two lines \n\n
Copy the code

The data field

Data content is represented by the data field.

data:  message\n\n
Copy the code

If the data is long, it can be divided into multiple lines, ending with \n\n on the last line and ending with \n on all previous lines.

data: begin message\n
data: continue message\n\n
Copy the code

Here is an example of sending JSON data.

data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
Copy the code

Id field

Data identifiers are represented by an ID field, which corresponds to the number of each piece of data.

id: msg1\n
data: message\n\n
Copy the code

The browser reads this value with the lastEventId property. Once the connection is broken, the browser sends an HTTP header containing a special last-event-ID header that sends back the value to help the server rebuild the connection. Therefore, this header can be thought of as a synchronization mechanism.

The event field

The event field represents a custom event type, which defaults to message. The browser can listen for this event with addEventListener().

event: foo\n
data: a foo event\n\n

data: an unnamed event\n\n

event: bar\n
data: a bar event\n\n
Copy the code

The code above creates three pieces of information. The first, named foo, triggers the foo event in the browser; The second, unnamed, is the default type that triggers the browser’s message event. The third is bar, which triggers the browser’s bar event.

Here’s another example.

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}

event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
Copy the code

Retry field

The server can use the Retry field to specify the interval at which the browser reinitiates the connection.

retry: 10000\n
Copy the code

The browser can re-initiate the connection in two cases: one is that the interval expires, and the other is that the connection fails due to network errors.

Node Server instance

SSE requires that the server remain connected to the browser. Different server software consumes different resources. Apache server, each connection is a thread, to maintain a large number of connections, is bound to consume a lot of resources. Node uses the same thread for all connections, so the resource consumption is much smaller, but this requires that each connection not contain time-consuming operations, such as I/O reads and writes to disks.

Here is an example of a Node SSE server

var http = require("http"); http.createServer(function (req, res) { var fileName = "." + req.url; if (fileName === "./stream") { res.writeHead(200, { "Content-Type":"text/event-stream", "Cache-Control":"no-cache", "Connection":"keep-alive", "Access-Control-Allow-Origin": '*', }); res.write("retry: 10000\n"); res.write("event: connecttime\n"); res.write("data: " + (new Date()) + "\n\n"); res.write("data: " + (new Date()) + "\n\n"); interval = setInterval(function () { res.write("data: " + (new Date()) + "\n\n"); }, 1000); req.connection.addListener("close", function () { clearInterval(interval); }, false); }}), listen (8844, "127.0.0.1");Copy the code