You get a bonus - 1 coin for daily activity. Now you have 1 coin

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE

Lecture



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

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE
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

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE
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

HTTP streaming

The next option is to consider HTTP streaming, where several responses can be sent to one request, as shown in the figure below. This is an effective mechanism, but unfortunately not universally acceptable in all proxy / firewall configurations, which makes it unsuitable for widespread deployment.

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE

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
- Network crash management controls browser
Cons compared to Long Polling:
- HTTP is not a compatible protocol, you need your server, debugging is complicated

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE


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

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE
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, as done 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: 10,000 \ 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) {
   // Send some data
   console.log (e.data);
 }, false);

 source.addEventListener ('open', function (e) {
   // Connection was opened
 }, false);

 source.addEventListener ('error', function (e) {
   if (e.eventPhase == EventSource.CLOSED) {
     // Connection is 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?

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 () { // I set the timeout, not $ .ready, otherwise webkits will have infinite loading
 }, 50);


Requesting username:

 // Get the name from localStorage and request a new one.
 var name = (prompt ('Name:', window.localStorage? window.localStorage ['name'] || '': '') || 'anonymous'). substr (0, 20);

 // Trying to save the name
 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);

 // Listen to the EventSource event - "message"
 eventSrc.addEventListener ("message", function (event) {
     var data = JSON.parse (event.data);
     // Draw the message from the server
     renderMessage (data);
 }, false);

 // Listen to the EventSource event - "error"
 eventSrc.addEventListener ("error", function (event) {
     // Report a connection problem
     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;

 // Cache static (index.html we will give with 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) {
         // Helm the correct headers
         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 ();

         // Helm special title for EventSource
         response.writeHead (200, {'Content-Type': 'text / event-stream'});

         // Set a longer timeout for the socket, otherwise the socket will close in 2 minutes
         request.socket.setTimeout (1000 * 60 * 60);  // 1 hour

         // If the connection fell - delete this client
         request.on ('close', function () {
             Clients.remove (clientId);
         });

         // Add client to the list
         Clients.add (clientId, response, name);
     }


3. Message from customer:

     'POST / message': function (request, response) {
         var data = '';
         // POST BODY COME
         request.on ('data', function (chunk) {
             data + = chunk;
         });

         // All the pieces of the POST body are collected
         request.on ('end', function () {
             // Parsing body
             data = qs.parse (data);

             // Sending a message to everyone
             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 ++;

         // Send messages on behalf of the bot
         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) {
         // If there is no client, then do nothing
         var client = this ._clients [clientId];
         if (! client) {
             return ;
         }
         // Close the connection
         client.response.end ();
         // Remove the client
         delete this ._clients [clientId];
         this .count--;
        
         // We inform all remaining that he left
         // Send messages on behalf of the bot
         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 ;
         }
         // Prepare the message
         var data = JSON.stringify ({
             message: message.substr (0, 1000),
             name: (name || 'anonymous'). substr (0, 20),
             isbot: isbot ||  false
         });

         // Create a new buffer so that with a large number of clients
         // Return was faster due to the peculiarities of the Node.js architecture
         data = new Buffer ("data:" + data + "\ n \ n", 'utf8');  // Format Message SSE

         // Send to everyone
         clients.forEach ( function (client) {
             client.response.write (data);  // Send buffer
         });
     }


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

Create and enable the server

 // Create server
 var httpServer = http.createServer ( function (request, response) {
     var key = request.method + '' + parse (request.url) .pathname;

     // If there is no route, then give the default Routes. $ - 404
     (Routes [key] || Routes. $) (Request, response);
 });

 // Turn on the server
 httpServer.listen (80);
 console.log ('Online'); 


Source code server.js https://github.com/azproduction/event-source-chat/blob/master/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.
Comparison of data transfer methods

Real-Time Applications and Polling, Long Polling, WebSockets, Server-Sent Events SSE

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 leaving the chat, there may be frequent reconnect)
2. Sample source: github.com/azproduction/event-source-chat


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Running server side scripts using PHP as an example (LAMP)

Terms: Running server side scripts using PHP as an example (LAMP)