On July 14, 1789, citizens of Paris stormed the Bastille, igniting a revolution that toppled the French monarchy. On July 14 of this year, there was a less dramatic (though more tweeted) takedown: The Deck network, which delivers advertising to some of the most popular web design and culture destinations, was down for about thirty minutes. During this period, most partner sites running ads from The Deck could not be viewed as result.
A few partners were unaffected (aside from not having an ad to display). Fortunately, Dribbble, was one of them. In this article, I’ll discuss outages like this and how to defend against them. But first, a few qualifiers: The Deck has been rock solid – this is the only downtime we’ve witnessed since joining in June. More importantly, the issues in play are applicable to any web widget you might add to your site to display third-party content.
Down and out
Imagine a widget that prints out a Pun of the Day on your site. A simple technique for both widget provider and consumer is for the provider to expose a URL:
document.write("<h2>The Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");
The call to
document.write() injects the string passed into the document where it is called. So to display the widget on your page, simply add an external script tag where you want it to appear:
<div class="punoftheday"> <script src="http://widgetjonesdiary.com/punoftheday.js"></script> <!-- Content appears here as output of script above --> </div>
This approach is incredibly easy for both provider and consumer. But there are implications…
document.write()… or wrong?
As in the example above, scripts may perform a
The elegant solution
To make our web widget more robust, calls to
document.write() should be avoided. This can be achieved with a technique called JSONP (AKA JSON with padding). In our example, instead of writing inline with
document.write(), a JSONP script passes content to a callback function:
publishPun("<h2>Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");
Then, it’s up to the widget consumer to implement a callback function responsible for displaying the content. Here’s a simple example where our callback uses jQuery to write the content into a target
Even if the widget content appears at the top of the page, our script can be included at the bottom so it’s non-blocking: a slow response leaves page rendering unaffected. It simply invokes the callback which, in turn, writes the widget content to its display destination.
But what to do if your provider doesn’t support JSONP? This was our case with The Deck. Returning to our example, I’m reminded of computer scientist David Wheeler’s statement, “All problems in computer science can be solved by another level of indirection… Except for the problem of too many layers of indirection.”
In our case, the indirection is to move the widget content into position after writing it to the page. This allows us to place the widget
<script> tag at the bottom of the page so rendering won’t be blocked, but still display the widget in the target. The strategy:
- Load widget content into a hidden
<div>at the bottom of the page.
- Move the loaded content from the hidden
<div>to its display location.
and the code:
After the external punoftheday.js script has processed, the rendered HTML will look as follows:
<div class="loading-dock hidden"> <script src="http://widgetjonesdiary.com/punoftheday.js"></script> <h2>Pun of the Day</h2> <p>Where do frogs go for beers after work? Hoppy hour!</p> </div>
<div> now includes the widget content, albeit hidden from view (if we’ve styled the ‘hidden’ class with
display: none). There’s just one more step: move the content to its display destination. This line of jQuery (from above) does the trick:
This selects all child elements in the ‘loading-doc’
<div> except the first – the widget
<script> tag which generated it – and moves it to the display destination. Worth noting is the
:gt(0) jQuery selector extension, which allows us to exclude the first (in a 0-based array) child element – the widget
<script> tag – from selection.
Since all of this happens at the bottom of the page, just before the
</body> tag, no rendering has to wait on the external widget script. The only thing that fails if our widget hangs is… the widget itself. Our weakest link has been strengthened and so has our site. DE-FENSE!