Web Performance Calendar

The speed geek's favorite time of year
2015 Edition
ABOUT THE AUTHOR

Dean Hume

Dean Hume (@DeanoHume) is a software developer and author based in London, U.K. He is passionate about web performance, and he regularly writes on his blog - deanhume.com. He is the recent author of Fast ASP.NET Websites, a book aimed at improving the performance of ASP.NET applications.

If you are building a website today there’s a good chance that you rely on 3rd party libraries to provide you with extra functionality. Tracking scripts, A/B testing and adverts are just a few of the many reasons why you would want to use a 3rd party library. The problem is that if the library that you use is hosted on another server, you risk creating a single point of failure (SPOF). If for any reason the server that is hosting these libraries goes down, or is slow to respond, your site will unfortunately be affected by this. For example, have a look at the video below.

This video is a recording showing what happened when you experience a SPOF. On the left of the video it shows the page without it being blocked by external scripts, and on the right of the video it shows the same page being affected by a SPOF. Notice the difference in load times! Even if you have a highly optimized site, it can take considerably longer to load your site because it will be blocked while it waits for the external resource to download. SPOF has been previously written about on the Performance Calendar, and it’s a problem that continues to plague us as developers.

Fortunately, we can use the power of Service Workers to reduce the risk associated with 3rd party libraries. Using Service Workers, we can force a resource to timeout if it takes too long to download. If you aren’t familiar with Service Workers, they are JavaScript Workers that run in the background, separate from a web page, and they open the door to features which don’t need a web page or user interaction. In this post, we will be using a feature of Service Workers that will allow us to intercept network requests and if they take longer than expected, we will be able to respond accordingly.

Imagine the following scenario – I have a simple web page and I am loading the Angular library from the Google CDN. If for any reason the Google CDN takes longer to load than expected, I can respond appropriately instead of blocking the load of my entire page. In order to get started with Service Workers, you’ll need to update your HTML file to reference a Service Worker file.

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>title</title>
 </head>
 <body>
    <script>
        // Register the service worker
        if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./spof.js').then(function(registration) {
            // Registration was successful
            console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(function(err) {
            // registration failed :(
                console.log('ServiceWorker registration failed: ', err);
            });
        }
    </script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
  </body>
</html>

In the code above, I am first checking if the browser supports Service Workers and if it does, I am registering a file named spof.js. You’ll also notice that at the bottom of the page I’ve included a reference to Angular hosted on the Google CDN. This is merely an example JavaScript file, you could use any JavaScript files.

Next, we need to create a JavaScript file called spof.js that will contain the code for the Service Worker.

function timeout(delay) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
          resolve(new Response('', {
              status: 408,
              statusText: 'Request timed out.'
          }));
        }, delay);
    });
}

self.addEventListener('fetch', function(event) {
    // Only fetch JavaScript files for now
    if (/\.js$/.test(event.request.url)) {
      event.respondWith(Promise.race([timeout(2000), fetch(event.request.url)]));
    } else {
      event.respondWith(fetch(event.request));
    }
});

The code above is pretty straightforward. I am adding an event listener to intercept any outgoing fetch requests. Next, I’m checking to see if the file is a JavaScript file – in your case this could be any file that is hosted on a 3rd party server. Then I am using an ES6 Promise race condition to determine if the fetch request is quicker than a set timeout period of 2 seconds (2000 milliseconds). The Promise race condition will then determine which of the functions finished first. If the request took longer than 2 seconds, we return a 408 response. However, if we downloaded the file before the 2 seconds, we simply return the successful resource. I’ve chosen 2 seconds as a timeout, but you could use any amount of time that you deemed appropriate.

That’s it! We can now easily test this in action. I find one of the best ways to do this is to use the built in throttling functionality in Google Chrome to simulate a slower network connection. Fire up your Developer Tools in Google Chrome and head over to the network tab. From there, check “disable cache” and choose an option for throttling from the drop down. In order to test, I chose from the presets – GPRS 50KB.

Chrome Developer Tools - Throttling

If you refresh the web page and view the network requests, you’ll be able to see the Service Worker kick in.

SPOF - Service Workers

In this case it returns a 408 HTTP response, but it we remove the throttling and it returns in a time faster than 2 seconds, we will see a successful 200 HTTP response.

It’s worth noting that Service Workers require HTTPS in order to work. Service Workers are equipped with some pretty powerful features that could be used for man-in-the-middle attacks, which is why they require HTTPS in order to work. If you’d like to test this example out while in development, Service Workers are designed to work over localhost. It’s also worth noting that in order for this functionality to work, the Service Worker will need to be installed first, which means that this functionality will only work for subsequent visits to your site and not first time visits.

That said, using Service Workers to reduce SPOF can be a useful way to ensure that your site is available and isn’t at the mercy of 3rd party servers. As with any new browser technology, not every browser currently supports Service Workers. However, the best thing about them is that if your browser doesn’t support them, you will still have a normal website as the functionality won’t kick in. As Bruce Lawson puts it: “It’s perfect progressive enhancement.

If you’d like to play around with the code in this post, I’ve created a repo on Github. There is a demo version of this example that you can use to test over at deanhume.github.io/Service-Workers-Fetch-Timeout. Fire up your developer tools as above and you’ll be able to see the code in action.

It would be great to see this functionality rolled out across more websites as it can be an easy way to reduce SPOF. If you’d like to learn more about Service Workers, I recommend heading over to the repo on Github. The example in this article could even be extended further to include caching. Jake Archibald wrote a really helpful article called the Offline Cookbook, which is a great guide to caching using Service Workers. Finally, I recommend taking a look at the Service Worker Cookbook created by the team at Mozilla for more useful Service Worker tips and tricks!

If you have the time, it is definitely worth watching Patrick Hamann’s talk entitled Embracing the network where the initial inspiration for this post came from.