Server sent events and browser limits
Asked Answered
M

4

54

I have a web application that listens for Server Sent Events. While I was working and testing with multiple windows open, things were not working and I banged my head for several times looking in the wrong direction: eventually, I realized that the problem was concurrent connections.

However I was testing a very limited number and even if I am running the test on Apache (I know, I should use node).

I then, switched browser and noticed something really interesting: apparently Chrome limits Server Sent Events connections to 4-5, while Opera doesn't. Firefox, on the other hand, after 4-5 simultaneous connections, refuses to load any other page.

What is the reason behind this? Does the limit only apply to SSE connections from the same source, or would it be the same if I were to test open them from a different domain? Is there any chance that I am misusing SSE and this is actually blocking the browsers, or this is a known behaviour? Is there any way around it?

Montespan answered 3/9, 2013 at 5:13 Comment(1)
in windows, this is controlled by a registry setting that IE, chrome, and firefox respect, and which limits all connections, not just SSE. I had the same problem with websockets... you can't make this up...Slighting
T
63

The way this works in all browsers are that each domain gets a limited amount of connections and the limits are global for your whole application. That means if you have one connection open for realtime communication you have one less for loading images, CSS and other pages. On top of that you don't get new connections for new tabs or windows, all of them needs to share the same amount of connections. This is very frustrating but there are good reasons for limiting the connections. A few years back, this limit was 2 in all browsers (based on the rules in (http://www.ietf.org/rfc/rfc2616.txt) HTTP1.1 spec) but now most browsers use 4-10 connections in general. Mobile browsers on the other hand still needs to limit the amount of connections for battery saving purposes.

These tricks are available:

  1. Use more host names. By assigning ex. www1.example.com, www2.example.com you get new connections for each host name. This trick works in all browsers. Don't forget to change the cookie domain to include the whole domain (example.com, not www.example.com)
  2. Use web sockets. Web sockets are not limited by these restrictions and more importantly they are not competing with the rest of your websites content.
  3. Reuse the same connection when you open new tabs/windows. If you have gathered all realtime communication logic to an object call Hub you can recall that object on all opened windows like this:

window.hub = window.opener ? window.opener.hub || new Hub() 4. or use flash - not quite the best advice these days but it might still be an option if websockets aren't an option. 5. Remember to add a few seconds of time between each SSE request to let queued requests to be cleared before starting a new one. Also add a little more waiting time for each second the user is inactive, that way you can concentrate your server resources on those users that are active. Also add a random number of delay to avoid the Thundering Herd Problem

Another thing to remember when using a multithreaded and blocking language such as Java or C# you risk using resources in your long polling request that are needed for the rest of your application. For example in C# each request locks the Session object which means that the whole application is unresponsive during the time a SSE request is active.

NodeJs is great for these things for many reasons as you have already figured out and if you were using NodeJS you would have used socket.io or engine.io that takes care of all these problems for you by using websockets, flashsockets and XHR-polling and also because it is non blocking and single threaded which means it will consume very little resources on the server when it is waiting for things to send. A C# application consumes one thread per waiting request which takes at least 2MB of memory just for the thread.

Thief answered 26/2, 2014 at 10:26 Comment(5)
Great answer! Do you know why Firefox was refusing to open any other domain whatsoever?Montespan
Thanks ; unfortunately the window.opener trick will only work for child windows that I create from my app ; not for any tab that the user opens herself. As soon as the user opens too many tabs, I'm screwed... Do you know if there is a way to check that we're about to exhaust all open connections ?Dosser
Try using (window.parent || window.opener) which will take care of both scenarios. Also look at the postMessage method for sending cross tab messages. I don't think there are any way of seeing amount of connections but you should rather use the timeout property to be able to react to scenarios where the long-polling request is stalling other requests. xhr = new XMLHttpRequest(); xhr.timeout = 5000; xhr.ontimeout=timeoutFired;Thief
Sunyatasattva - yes - ALL connections are included - even new pages. Which means if you have four open tabs, each with its own long-polling connection you have used all your connections and since the long-polling requests were going to the same domain the global limit for that domain was exhausted. Best way to prevent this is to use separate domain for the long-polling connection (or reuse connections), that means the ordinary page-load will not be affected - just the realtime connection.Thief
@ChristianLandgren Correct me if I'm wrong, but I don't think two separate tabs opened independently in a browser share window.parent ?Dosser
F
17

2022 Update

This problem has been fixed in HTTP/2. According to mozilla docs:- When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be especially painful when opening multiple tabs, as the limit is per browser and is set to a very low number (6).

The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, which means that you can open 6 SSE connections across all of the tabs to www.1.example and another 6 SSE connections to www.2.example (per Stackoverflow).

When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).

Spring Boot 2.1+ ships by default with Tomcat 9.0.x which supports HTTP/2 out of the box when using JDK 9 or later.

If you are using any other backend, please enable http/2 to fix this issue.

Festal answered 12/6, 2022 at 9:39 Comment(0)
S
10

One way to get around this issue is to shut down the connections on all the hidden tabs, and reconnect when the user visits a hidden tab.

I'm working with an application that uniquely identifies users which allowed me to implement this simple work-around:

  1. When users connect to sse, store their identifier, along with a timestamp of when their tab loaded. If you are not currently identifying users in your app, consider using sessions & cookies.
  2. When a new tab opens and connects to sse, in your server-side code, send a message to all other connections associated with that identifier (that do not have the current timestamp) telling the front-end to close down the EventSource. The front-end handler would look something like this:

    myEventSourceObject.addEventListener('close', () => { myEventSourceObject.close(); myEventSourceObject = null; });

  3. Use the javascript page visibility api to check to see if an old tab is visible again, and re-connect that tab to the sse if it is.

    document.addEventListener('visibilitychange', () => { if (!document.hidden && myEventSourceObject === null) { // reconnect your eventsource here } });

  4. If you set up your server code like step 2 describes, on re-connect, the server-side code will remove all the other connections to the sse. Hence, you can click between your tabs and the EventSource for each tab will only be connected when you are viewing the page.

Note that the page visibility api isn't available on some legacy browsers: https://caniuse.com/#feat=pagevisibility

Stanwin answered 9/6, 2019 at 14:25 Comment(2)
Note that the page visibility api uses browser prefixes for some older browsers. Check out this small package on npm if you don't want to manually implement them: npmjs.com/package/visibilityjsStanwin
Bad suggestion. You can't stop the connection because the user will stop of get real time notifications. if the user reconnect again it should be update the page to get the news.Bartle
H
3

You are right about the number of simultaneous connections.

You can check this list for max values: http://www.browserscope.org/?category=network

And unfortunately, I never found any work around, except multiplexing and/or using different hostnames.

Hallerson answered 25/2, 2014 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.