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:
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
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
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.
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
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.
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
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
Running server side scripts using PHP as an example (LAMP)
Terms: Running server side scripts using PHP as an example (LAMP)