Avoid Using EventSource (Server-Sent Events)

On January 30, 2012, in Javascript, by LuCuS

Google Chrome LogoEarlier this month, I told you about a new project I’m working on for a real-time collaboration platform. During the early stages of development, I tested several technologies to use for communicating with the platform from a web browser. Well, today I want to talk about one of those technologies and the reason it wasn’t selected. I’m talking about “EventSource” (aka “Server-Sent Events” or “SSE” for short). Even though SSE is cool and fun to learn, I would highly recommend staying away from it for anything running in a production environment. Let me explain why.

To begin with, what are “server-sent events”? Basically, SSE is a one-way technology used for streaming content from a server to the browser. Until recently, web browsers were not designed with the intent of listening for messages from a server. Instead, they were designed to call a server when they wanted something and disconnect once the data had been received. For today’s purposes, this isn’t enough. So, technologies like SSE were created that will “listen” for messages that are sent from the server.

Take chat rooms for example. Chat rooms are intended to allow multiple users to connect to a central server where messages can be posted and re-posted to all connected clients. Until recently, browsers would have to continuously refresh, making new requests each time, back to the server to check for messages posted from other users. As you can see, this is not a very efficient way of handling things like this. Instead, technologies like SSE will make one connection to a server and will listen for any message posted from any user.

But, like I said earlier, SSE is a “one-way” technology. Well, what does that mean? This means that SSE is only designed to “listen” for messages sent from the server. SSE is not designed to post messages back to the server. Technologies such as WebSockets allow for a two-way communication over a single connection. So, you can probably already see one of the reasons why SSE was not the technology of choice for my real-time collaboration platform.

Aside from the fact that SSE is a one-way technology, there are still a few other reasons why I suggest avoiding SSE. For example, SSE is not supported by all browsers. In fact, Chrome is the only browser I know of that fully supports EventSource. So, unless you plan on limiting your users to only using Chrome, you will have to write your own version of EventSource, which I did just for fun. But, that’s for another article. Ok. SSE is a one-way technology and isn’t cross-browser compatible. That makes 2 reasons why I suggest avoiding SSE. What else?

Even though SSE isn’t cross-browser compatible, I decided to make my own version which is. But still, that too isn’t enough. The next reason for avoiding SSE is probably the biggest. Apparently, EventSource has a huge memory leak. Now, before you start sending me hate mail claiming that the memory leak only exists in my hand-written version of EventSource, I have proven that the memory leak also exists in Chrome’s native EventSource and I provide a test case to prove that at the end of this article.

So, what is this memory leak I speak of? Well, in order to make SSE work, you have to bind a connection to the server that never closes. And this is where the memory leak comes in. Once you bind that persistent connection, the server will continuously pump down data thru that connection back to the client. If not handled properly, this will cause your browser to eat up all of your system resources until the browser crashes.

You’ll probably notice that I said “if not handled properly”. If done correctly, SSE can have less of an impact on your browser. For example, when you log into Facebook and sit there for a while, if someone else sends you a message, your browser will notify you without requiring any interaction from you such as refreshing your browser. To do that using SSE, you could store a variable for the last message sent or add an ID to your messages. Then, when a new message is ready to be sent, you can compare the 2 messages. If the messages or message IDs are alike, you can simply ignore the message and not send it to the client. If the messages are different, you can send the new message to the client and set the value of the last message to that of the new message so that upon the next iteration of your messages, the new message will not be duplicated and re-sent to the client.

But, what if you wanted to create a page that monitors the performance of your server? Maybe you want to have your server tell you how many clients it has connected to it or how much memory your server is consuming. Well, since these are variables that can and will most likely change quite often, this is where the memory leak comes in when working with SSE. Since that data will continuously be sent to the client, the browser will be constantly collecting data until it runs out of resources and “KABOOM!” The browser crashes. Let’s take a look at an example of this.

To prove this memory leak exists, I have created a simple HTTP server using Python and have created a test client using HTML and Javascript. Since I won’t be including my custom version of EventSource, the client used in this example will only work in Chrome. So, let’s begin.

In order for SSE to work, you have a couple of guidelines you must follow. The first is that you must return the content-type header as “text/event-stream”. Next, you must return a specific body structure to the client where each variable is written on a separate line. For example, one line must include the event your client will be listening for. In this example, my event is called “message”. So, I will return “event: message” in the response body. Then, on another line, I can include things such as message IDs or whatever I want. But, the last line needs to be a variable for “data”. This variable will contain any content you want posted to the client. For this example, my “data” variable will contain a timestamp which Python will provide me.

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
import time

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('content-type', 'text/event-stream')
        self.end_headers()
        while True:
            self.wfile.write('event: message\nid: 1\ndata: {0}\ndata:\n\n'.format(time.time()))
        
serveraddr = ('', 9292)
srvr = HTTPServer(serveraddr, RequestHandler)
srvr.serve_forever()

As you can see, the server listens on all IP addresses on port 9292 for incoming requests. Once a connection is established, it returns a 200 success response with header “content-type” of “text/event-stream”. Then, it goes into a loop where it continuously sends the time to the client. It’s a very simple example. So, you shouldn’t have any problems figuring it out.

Next, we need to create a client that can listen for messages from the server. Again, this is a very simple example. So, you shouldn’t have any issues following it either. But,  I’ll still explain it anyways. First, you’ll see that I have a div with an id of “ticker”. This is where we will write the response from the server. Next, you’ll see that there’s a javascript variable for a new EventSource object. The only parameter required for EventSource is the URL to the server you will be calling. After that, I’ve added an event listener that will fire every time EventSource gets an event from the server. As I mentioned earlier, I am using the event name “message”. But, you can change this to whatever you want as long as you also change it in the server. Inside that event handler, I simply take the response from the server and plug that into the div we built earlier. So, when we run this example, the server will return the time which is written into the “data” variable. Once that’s received, the div that reads “[TIME]” will show the the server time instead of the placeholder “[TIME]“. Here is my client which I named “sseclient.html”:

<html>
<body>
<div id="ticker">[TIME]</div>

<script type="text/javascript">
   var source = new EventSource('http://localhost:9292/');
   source.addEventListener("message", eventHandler, false);
   function eventHandler(event)
   {
       document.querySelector('#ticker').innerHTML = event.data;
   }
</script>
</body>
</html>

Ok. Now we have our client built. However, if you run this from your filesystem as-is, you’re going to encounter the good ‘ol DOM Exception: “Uncaught Error:SECURITY_ERR: DOM Exception 18“. This occurs because you are attempting to use Javascript to call an external domain. Even if you run this example client from “localhost”, by changing the port number, the browser will see this as a different domain and will throw the DOM exception. In order to get around this, you will need to create some sort of proxy using something like PHP and Curl. However, instead of going thru all of that, I chose to go with a simpler approach and use some of the resources I already had available. Since I already had Apache installed on my computer, I opened Apache’s httpd.conf file (located in “Apache Install Folder/conf/”) and enabled “mod_proxy” and “mod_rewrite”. After I restarted Apache, I copied my sseclient.html example file into the root “htdocs” folder of Apache so that I could call my file from the URL “http://localhost/sseclient.html”.

But, that too isn’t enough to bypass the DOM exception. In the “htdocs” folder, I created a file called “.htaccess”. Make sure you include the beginning “.” (period) before the file name. Inside that file, I checked to make sure mod_rewrite was available and enabled the rewrite engine if it was. Next, I added my rewrite rule which pointed to my Python server running on localhost on port 9292. Here is the entire contents of my “.htaccess” file:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule client http://localhost:9292/ [NC,L,P]
</IfModule>

You’ll notice that my rewrite rule includes the word “client”. This is the URL pattern I want to watch for and proxy all requests to my Python server. So, back in sseclient.html,  I had to change the URL parameter of EventSource to match. Here is what that now looks like:

var source = new EventSource(‘/client’);

Alrighty. That’s everything you need to test this memory leak. So, to test it, start your Python server. Then, open up Chrome and press the F12 key to open the debugger window. Next, click on the “Network” tab so that you can see all requests. Once you have the “Network” debugger window open, browse to “http://localhost/sseclient.html”. When you do that, you should see 2 requests in your Network window. One request should be for “sseclient.html” (or whatever you named your HTML client) and the other should be for “/client”. If everything worked correctly, you should not see “[TIME]” in your page. Instead, you should see a number that looks something like “1327935361.6″. You should also see the Timeline bar in your Network tab rising. At this point, you should also see the Size column for “/client” rising very quickly. In just a couple of seconds, the Size column for my “/client” jumped to over 10MB. If you open Task Manager, you will see the memory utilization go thru the roof for Chrome there as well. These are the indicators that tell me EventSource has a memory leak.

If you have any questions or suggestions, please let me know in the comments below. In another post, I will share with you the technology and approach I used in place of server-sent events and EventSource.

Grab yourself a copy of my eBook (Android App Development 101) today!

BUY NOW

Related Posts

Tagged with:  

8 Responses to Avoid Using EventSource (Server-Sent Events)

  1. sgsfak says:

    Hi some comments:

    - re: “SSE is one way” -> There are plenty of use cases where one way (near) real time, communication is what you need. For example Facebook/twitter updates or just in the case of monitoring an application or a stream of events (e.g. stocks)

    - re: “SSE isn’t cross-browser compatible” -> Please check this: here. There is an almost universal support (IE will probably support in its upcoming (10) version). It is certainly not a Chrome-only feature.

    - re: “memory leaks” -> You do not mention what version of Chrome you are using. In any case this has nothing to do with the SSE mechanisms — I would expect to see the same behavior in any javascript application where you constantly create DOM nodes. GC will probably fire when the memory becomes low, unless there’s really a bug (memory leak) in the specific browser you are using.

    In my opinion SSE is a pretty, simple, and handy solution for server push use cases.

    Stelios

    • LuCuS says:

      Thank you for your feedback. When time permits, I’d like to take another look at this technology. I don’t remember what all browsers and versions I tested with, but it was several. With that said, it’s possible that I was running an older version of SSE and even on browsers that weren’t supported at the time. This article is solely based on my experiments when searching for something that works with my real-time collaboration platform. Do you have a live example of SSE in action that I could check out?

      • sgsfak says:

        I have used SSE for an internal application at my work. But you can find a demo linked from the SSE htm5 rocks tutorial.

        Stelios

        • LuCuS says:

          Cool. Thanks! I took a quick look at the demo and even though it rebinds the connection after every 3 connections, the memory consumption continues to climb. It doesn’t climb as fast as it does in the demo I provided above, but over enough time, it would still eat up all of the allocated resources. Tonight, I’m going to take a look at the SSE html5 rocks tutorial.

  2. richardcz says:

    Please help me. If I run following command from a command line: wget http://localhost:9292/ , I get a file which is blowing up. But if I open it in Firefox (12.0) on Ubuntu it does not understand of it and ask for an application. What can be a reason?

  3. ralphs says:

    Hi
    Thanks for the post. The problem in your case is not that EventSource has a memory leak it’s just that your response is getting big very fast.

    EventSource is a simple wrapper around a HTTP GET Request. And it expects a simple HTTP response with headers and a body.

    In your case the problem is the endless loop that creates a huge response.
    while True:
    self.wfile.write(‘event: message\nid: 1\ndata: {0}\ndata:\n\n’.format(time.time()))

    It’s the same as creating a dynamic php page that contains an endless loop and never stops sending html code back to browser.
    What you could do is throttling the output. For example the program could wait 1 second between every message. And the program should close the connection from time to time. For example the program could write the time for two minutes then close the connection. EventSource has this nice feature that it automatically reconnects when the connections ends. The default is 3 seconds, but the server can change this timeout.
    event: message\nid: 1\ndata: {0}\ndata:\nretry:500\n\n
    This example sets the timeout to 0.5 seconds. That means 0.5 seconds after the connection died or was closed EventSource tries to reconnect to the server.

    By the way you don’t need the last data:\n\n. You just have to make sure that the message ends with a . And you don’t need the event: message line. This is the default eventname. This way you could save a few bytes:
    self.wfile.write(‘id: 1\ndata: {0}\n\n’.format(time.time()))

    Because EventSource is using simple GET requests it’s possible to emulate it with XMLHttpRequest. There are a few polyfills out there that do this. This way it should work in almost every browser:
    https://github.com/remy/polyfills
    https://github.com/Yaffle/EventSource

    But most of the modern browser support EventSource natively:
    http://caniuse.com/#search=eventsource

    One nice thing about EventSource is the automatic reconnect feature. This way you can build polling, long-polling or streaming solutions with this. Even it’s not a two way communication like Websocket you can build a chat solution with this. Just use EventSource for Server->Client and simple XMLHttpRequests for Client->Server communication. And compared to Websockets this runs in almost every browser.
    Websocket is definitely a nice solution but it will take a while until every user has a browser that support it.

    • LuCuS says:

      Thanks for the feedback. I’ve received several emails about this topic and have had plans to take another look at EventSource. I just haven’t had the time or worked on any projects that could use it. Hopefully I’ll find some time in the near future to take another look at it and will definitely keep your feedback in mind.

Leave a Reply