I am building a webapp and serving it over http2. However when I analyze network in Google Chrome (Version 59.0.3071.115 (Official Build) (64-bit))\'s developers tools, it i
I think this is a bug in Chrome, or at least a needless restriction.
This is easily tested.
I created a simple example HTML file, which downloads 25 copies of the same javascript file (with a query param to make it look like a different resource):
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Lots of JS files</title>
<meta name="robots" content="noindex">
<body>
</body>
<h1>This is a test for Lots of JS files</h1>
<script src="/assets/js/test.js?v=01"></script>
<script src="/assets/js/test.js?v=02"></script>
<script src="/assets/js/test.js?v=03"></script>
<script src="/assets/js/test.js?v=04"></script>
<script src="/assets/js/test.js?v=05"></script>
<script src="/assets/js/test.js?v=06"></script>
<script src="/assets/js/test.js?v=07"></script>
<script src="/assets/js/test.js?v=08"></script>
<script src="/assets/js/test.js?v=09"></script>
<script src="/assets/js/test.js?v=10"></script>
<script src="/assets/js/test.js?v=11"></script>
<script src="/assets/js/test.js?v=12"></script>
<script src="/assets/js/test.js?v=13"></script>
<script src="/assets/js/test.js?v=14"></script>
<script src="/assets/js/test.js?v=15"></script>
<script src="/assets/js/test.js?v=16"></script>
<script src="/assets/js/test.js?v=17"></script>
<script src="/assets/js/test.js?v=18"></script>
<script src="/assets/js/test.js?v=19"></script>
<script src="/assets/js/test.js?v=20"></script>
<script src="/assets/js/test.js?v=21"></script>
<script src="/assets/js/test.js?v=22"></script>
<script src="/assets/js/test.js?v=23"></script>
<script src="/assets/js/test.js?v=24"></script>
<script src="/assets/js/test.js?v=25"></script>
</html>
I then did the same, but adding the async attribute, in case Chrome decide to block downloading while processing the Javascript:
<script src="/assets/js/test.js?v=01" async=""></script>
<script src="/assets/js/test.js?v=02" async=""></script>
....etc.
and the same again but with the defer attribute:
<script src="/assets/js/test.js?v=01" defer=""></script>
<script src="/assets/js/test.js?v=02" defer=""></script>
....etc.
The /assets/js/test.js
file was empty. So there would be no execution delays, nor dependencies except those that the browser added.
I saw some interesting results! This is all with Chrome 60.0.3112.78 or 60.0.3112.101, and I'm using Apache, but saw same results as you saw for Nginx.
With an HTTP/2 server we see the following results:
With a plain script
tag all the scripts are loaded in parallel (but presumably executed in order). There is no 6 connection limit as under HTTP/1.1:
With an async script
tag the scripts are loaded in parallel in groups of 6 - exactly as you noted:
Clicking on them shows they WERE downloaded over HTTP/2.
With a defer script
tag the scripts is the same as the results for using the async tag - a throttling to 6 downloads at a time.
This does not make sense - Chrome is restricting your Javascript downloads, but only if you use async or defer to improve your downloads from blocking rendering!
As sbordet stated, the same does not happen for images in the viewport - so multiplexing DOES work on Chrome, it just appears to be needlessly limited for Javascript in async or defer mode. This is a real limitation, if you are considering not bundling scripts together any more under HTTP/2, as many advise you no longer need to do.
The same does not happen on Firefox, nor Edge. Though it does happen on Opera (a Chromium based browser).
So that's the bad news. The good news is that they "may" have fixed it. When I try Chrome Canary (62.0.3190.0) I can't repeat this behaviour. However when I use Web Page Test with Canary (which it gives 62.0.3190.1 in the user agent string, so should be practically the same) it is repeatable, so not 100% sure they have fixed this after all...
Have raised a bug with the Chrome team for this so will see what they say: https://bugs.chromium.org/p/chromium/issues/detail?id=757191
All in all, HTTP/2 on both server and client does seem a little in flux at the moment, as both sides tweak and tune their implementations to get optimal use out of this still relatively new protocol. Still, it's surprising to see Chrome hit with this since Google started this off with their SDPY implementation (which HTTP/2 is heavily based upon) so you would expect them to be ahead of the curve not behind...
** Update **
Chrome team got back and confirm this is a restriction of current implementation of HTTP/2 in Chrome. They were seeing performance issues when many assets very called at once, as HTTP/2 allows, so restrict non-critical items (including async/defer and items not visible in the viewport) to HTTP/1.1 limit of 6.
Even though HTTP/2 has concept of prioritisation of requests after they are sent, the performance issues were seen before they were prioritised and sent (e.g. checking cache, cookies... etc) so HTTP/2 prioritisation doesn't help here.
They hope to improve this in future.
So guess I was right that it's an implementation issue as we get used to the new HTTP/2 world and have to optimise our browsers and servers for it!
There may be multiple reasons why Chrome decides to limit multiplexing when using HTTP/2.
For example, the behavior is very different when you are downloading a page with a large number of images, depending on whether the images are shown or not in the browser viewport.
The documents you are downloading are scripts and scripts may block, or depend on each other, or otherwise change the way the browser downloads resources.
In fact, if you go to online examples of HTTP/2 such as https://http2.golang.org/gophertiles?latency=0, you will see that Chrome does multiplex really well the download of images (but only if they are displayed in the viewport).
Therefore for your case it could be something with the scripts; perhaps they have dependencies on each other, and that is why Chrome cannot multiplex them beyond 6 at a time.
I would not be surprised if this is a limit of JavaScript loaders that assume HTTP/1.1 and are now obsolete with HTTP/2.
You can use the "Performance" tab in the Chrome Developer Tools to understand more about the performance of your page.
You also want to look at tools such as Page Speed, that give you an idea of how to optimize your page.
In summary, I don't think it's an issue with how Chrome implements HTTP/2, but rather something in your application/scripts that is not optimized for HTTP/2.