📜 ⬆️ ⬇️

Creating real-time applications using Server-Sent Events

Just recently, it became known that Firefox 6 will receive SSE ( already in Opera 10.6+, Chrome, WebKit 5+, iOS Safari 4+, Opera Mobile 10+) so that more than half of all browsers (user reach) are no longer supported. by the mountains. It is time to look at this technology. SSE proposed Ian Hickson more than 7 years ago, but only a year ago it began to appear in browsers. We have WebSockets, why do we need another protocol? But everything has its pros and cons, let's see what SSE can be useful for.

The idea of ​​SSE is simple - the client subscribes to server events and as soon as an event occurs - the client immediately receives a notification and some data associated with this event. To understand the usefulness of the SSE protocol, it is necessary to compare it with the usual methods of receiving events, briefly explain their essence:

Polling


The simplest, but the most inefficient, method: the client polls the server once every few seconds for events. Even if there is nothing, then the client makes a request vseravno - and you never know what will come.
Pros:
- Simply
- Data can be shipped
Minuses:
- A lot of extra requests
- Events always come late
- The server has to store events until the client picks them up or until they become obsolete

Long polling


An improved version of the previous method. The client sends a request to the server, the server keeps the connection open until some data arrives or the client disconnects on its own. As soon as the data has arrived, the response is sent and the connection is closed and the next is opened and so on.
Pluses in comparison with Polling:
- Minimum number of requests
- High temporal accuracy of events
- The server stores events only for the time of reconnect
Cons compared to Polling:
- More complicated circuit
')

WebSockets


This is a binary duplex protocol that allows the client and server to communicate on an equal footing. This protocol can be used for games, chats and all those applications where you need extremely accurate events close to real time.
Pluses in comparison with Long Polling:
- One connection rises
- Extremely high temporal accuracy of events
- Manage network failures controlled by browser
Cons compared to Long Polling:
- HTTP is not a compatible protocol, you need your server, debugging is complicated

So why is it worth using SSE, since we have such a great protocol WebSockets ?! First, not every web application needs two-way communication — SSE will do. Secondly, SSE is an HTTP compliant protocol and you can implement event broadcasting on any web server.

Server-Sent Events Protocol


The client sends a request to the server, the server sends the following header in response:
Content-Type: text/event-stream 

And it does not close the connection (on php you can create an infinite loop, how to do it on node.js will be explained in the sample article). That's it - SSE works! To send some data to the client, the server simply writes a string of the following format to the socket:
 data: My message\n\n 

If you need to send several lines of data, the format will be as follows:
 data: {\n data: "msg": "hello world",\n data: "id": 12345\n data: }\n\n 

Here, in principle, and the entire base of the protocol. In addition, the server can send an id message, it is necessary in case the connection was terminated. If the connection was dropped, the client will send a special header (Last-Event-ID) when trying to connect to recover lost events:
 id: 12345\n data: GOOG\n data: 556\n\n 

Retry time in case of errors:
 retry: 10000\n data: hello world\n\n 

The id and retry fields are optional.

On the client, everything will look like this:
 var source = new EventSource('http://localhost/stream.php'); source.addEventListener('message', function(e) { //  -  console.log(e.data); }, false); source.addEventListener('open', function(e) { //    }, false); source.addEventListener('error', function(e) { if (e.eventPhase == EventSource.CLOSED) { //   } }, false); 

Everything is very simple. Let's build an application based on the SSE protocol. As usual, it will be a chat.

Multipart XMLHTTPRequest


Also called multipart streaming (Supports only Firefox). Very similar to SSE protocol.
Its title has a format:
 Content-type: multipart/x-mixed-replace;boundary=smthing 

And the parts are sent in this format:
 Content-type: text/html\r\n\r\n --smthing\n Message\n --smthing\n 


The client creates a regular XHR, but before sending the request, you must set the flag req.multipart = true;
Does it look like SSE? Read more

There is another protocol that can lead to SSE:

XMLHTTPRequest: Interactive


To use it, browser support for the special readyState with code 3 (interactive) is necessary - this status indicates that part of the data has arrived, but the connection has not yet been closed. For jQuery, there is a plugin of the same name that uses readyState with code 3. And as always, not all browsers support readyState with code 3.

Example: Chat on Server-Sent Events


We will receive a stream of events on SSE: going offline, coming online, posting. Because SSE can not send a message, then we will send them via HTTP.

The scheme of work is as follows:
- At the entrance to the chat name is requested
- The client connects to the chat server. It creates a stream of events.
- When a client connects, the chat sends to all the event:% username% online
- When a client disconnects, the chat sends to all the event:% username% offline
- The client can send a message via HTTP "POST / message" The server accepts this message and sends the received message to all clients via SSE

Let's sort the client code. In order for some browsers not to have an infinite download, instead of $ .ready, we execute setTimeout:
 setTimeout(function () { //   ,   $.ready       }, 50); 

Requesting username:
 //    localStorage    var name = (prompt('Name:', window.localStorage ? window.localStorage['name'] || '' : '') || 'anonymous').substr(0, 20); //    if (window.localStorage) { window.localStorage['name'] = name; } 

Create an EventSource and pass it the user name (now the user is online) and listen to the necessary events:
 var eventSrc = new EventSource("/event?name=" + name); //   EventSource - "message" eventSrc.addEventListener("message", function(event) { var data = JSON.parse(event.data); //      renderMessage(data); }, false); //   EventSource - "error" eventSrc.addEventListener("error", function(event) { //      renderMessage({ isbot: true, message: 'connection error', name: '@Chat' }); }, false); 

I will not consider the renderMessage method and page layout. All client code can be viewed here: index.html

On the server side we will have Node.js. Everything is more complicated here, but the main difficulty in multicast messages from one user to all, and not in building communication via SSE.

We connect the necessary modules

 var http = require('http'), fs = require('fs'), qs = require('querystring'), parse = require('url').parse; //   (index.html      Node.js) var indexFile = fs.readFileSync('index.html'); // Buffer 

Routs

Create a list of routes Routes, which includes the following objects:
1. Static. Index page, we just helmet statics:
  'GET /': function (request, response) { //    response.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'}); response.write(indexFile); response.end(); } 

2. Raising the SSE connection:
  'GET /event': function (request, response) { var url = parse(request.url, true); var name = (url.query.name || 'anonymous').substr(0, 20); var clientId = Clients.generateClientId(); //     EventSource response.writeHead(200, {'Content-Type': 'text/event-stream'}); //     ,     2  request.socket.setTimeout(1000 * 60 * 60); // 1  //    -    request.on('close', function () { Clients.remove(clientId); }); //     Clients.add(clientId, response, name); } 

3. Message from customer:
  'POST /message': function (request, response) { var data = ''; //    POST request.on('data', function (chunk) { data += chunk; }); //   POST   request.on('end', function () { //   data = qs.parse(data); //    Clients.broadcast(data.message, data.name, false); response.writeHead(200); response.end(); }); } 

4. Route by default - Page 404:
  $: function (request, response) { response.writeHead(404); response.end(); } 

Client Manager - Clients

When adding a new client (add), the manager sends the entire message that the client has arrived:
  add: function (clientId, response, name) { this._clients[clientId] = {response: response, name: name || 'anonymous'}; this.count++; //      this.unicast(clientId, 'Hello, ' + name + '! Online ' + this.count, '@ChatBot', true); this.broadcast(name + ' online', '@ChatBot', true); } 

When deleting closes the connection and sends to all that the client is offline:
  remove: function (clientId) { //   ,     var client = this._clients[clientId]; if (!client) { return; } //   client.response.end(); //   delete this._clients[clientId]; this.count--; //   ,    //      this.broadcast(client.name + ' offline', '@ChatBot', true); } 

To send messages to clients, use the private _send method:
  _send: function (clients, message, name, isbot) { if (!message || !name) { return; } //   var data = JSON.stringify({ message: message.substr(0, 1000), name: (name || 'anonymous').substr(0, 20), isbot: isbot || false }); //   ,      //     -   Node.js data = new Buffer("data: " + data + "\n\n", 'utf8'); //   SSE //   clients.forEach(function (client) { client.response.write(data); //   }); } 

The _send method uses public broadcast and unicast methods to send messages to all and one client, respectively.

Create and enable the server

 //   var httpServer = http.createServer(function (request, response) { var key = request.method + ' ' + parse(request.url).pathname; //   ,     Routes.$ - 404 (Routes[key] || Routes.$)(request, response); }); //   httpServer.listen(80); console.log('Online'); 

Source server.js

Our SSE chat is ready. We start the server:
 $ node server.js 

Open one of the browsers: Firefox 6, Opera 10.6+, Chrome, WebKit 5+, iOS Safari 4+, Opera Mobile 10+. Go to http:// localhost/ and chat!

Conclusion


SSE is a good technology that should replace Long Poling; it is simple and no less effective than WebSockets. Now SSE supports Opera 10.6+ (Opera 9 supports the old SSE standard), Chrome, Safari 5+. Firefox supports Multipart XMLHTTPRequest, for which you can write a wrapper and use it as an SSE interface.

Links


1. An online example of SSE chat can be viewed here: sse-chat.nodester.com
This is a somewhat stripped-down version of the chat due to the peculiarities of Nodester proxying (there is no message about the number of users online and no messages about quitting the chat, there may be frequent reconnect)
2. Sample source : github.com/azproduction/event-source-chat
3. Another SSE Tutorial
4. Specification

PS It seems that the chat has covered habraeffekt, but something is possible with nodester (it often happens at it). If you are interested in the result, then download the source from GitHub.

UPD Added Multipart XMLHTTPRequest, XMLHTTPRequest: Interactive thanks for the addition yui_room9

Source: https://habr.com/ru/post/120429/


All Articles