Slow and Inconsistent: Client-Side APIs (Part 2)
by August 13, 2013

Filed under: Performance Monitoring

AppNeta no longer blogs on DevOps topics like this one.

Feel free to enjoy it, and check out what we can do for monitoring end user experience of the apps you use to drive your business at www.appneta.com.

With contributions by Bobby Fitzgerald

In part 1, we discussed how application-based APIs can affect web apps, and a few ways to mitigate that. In this post, we’ll talk about the other side of the coin: client-side APIs.

Client Side API Tutorial

For better or for worse, the world of client-side APIs is actually much more limited. Browsers have different security needs than a server, and one of the oldest tools to prevent maliciously mixing data is the Same-origin policy. This effectively prohibits most interactions between a page and external resources, even if they might be interesting or valuable to the user. In the early days of the internet, this didn’t pose a problem. Most pages were just that: pages, with content and a few links. Modern web apps, on the other hand tend to pull data from more sources, and to facilitate this, we’ve developed a number of common workarounds.

Exceptions to same-origin

There are 4 notable exceptions to the same-origin policy:

  • Images
  • Stylesheets
  • Javascript includes
  • Form posts

At first glance, these are all pretty harmless. The first 3 are just loaded resources, so there’s no data exchanged in the same sense that an RPC would, for instance, update a user’s contact information. Form posting definitely can modify data, but like link navigation, changing pages implies that you can change domains as well. For strictly-standards-compliant webpages, these exceptions don’t leave much room for integrating with external services.

Let’s take a look at how we can bend the rules a bit for each one, and acheive an integration with a 3rd party.

Tracking Images

When requesting an image, browsers will always use HTTP GET, which says that the request “SHOULD NOT have the significance of taking an action other than retrieval”. Fortunately, the HTTP spec is more what you’d call guidelines than actual rules. Consider the following img tag:

<img alt="" src="https://beacon.tracelytics.com/LW6yeeyD-01n8maDNKEBPwZPHmU=/__tl.gif?url=https%3A//appneta.tv.appneta.com/overview%23&amp;v=0.3&amp;xt=1B0B7DEE67FCDC72948953617C01192708FF70DB09B625ABD6970980A6&amp;vid=T9uf08FjGLaZohOm&amp;ets=navigation%3D-1464%26domload%3D1044%26winload%3D1197" />

Woah, that’s a pretty complicated URL for an image! And what does it load? A single pixel, which isn’t even attached to the DOM. The reason is that we’re not meant to look at this image. This is part of TraceView’s Real User Monitoring. Earlier, the page loaded a bit of Javascript which used tools like the Navigation Timing API to figure out how long the page took to load. Of course, the same-origin policy prevents the browser from posting this back to AppNeta’s servers directly. Instead, we request a pixel with a specific set of query parameters: navigation, domload, and winload, each with the timing data we collected. The AppNeta beacon servers then record that data (which, strictly speaking, the HTTP spec wouldn’t approve of), and we’ve successfully integrated with a 3rd party!

What else would you do measurement data, other than draw graphs?

What else would you do measurement data, other than draw graphs?

This technique can be used to report all sorts of information. Timing data is actually some of the least nefarious; tracking pixels are the standard way of reporting back data for everything from Facebook and Twitter buttons to advertisers looking to retarget users. Remember the last time you looked at an online store, then their ads followed you for a week? They probably collected that data with tracking pixels on their website, which they used to update the cookie that spurred those ubiquitous ads.

Stylesheets and JS

Stylesheets and JS from external services generally do what they say on the tin. If your website wants to include JS from a 3rd party, it’s probably in the form of a library that you don’t want to host yourself. There are certainly tradeoffs to doing this, not least of which is the possibility of those CDNs going down. Sometimes, though, there are more pressing concerns than mitigating against Google being compromised or unavailable.

The major loophole in external JS includes is that externally included scripts run immediately, and all Javascript runs in the same scope. If you don’t trust exactly what you’re getting back from a 3rd-party script, this can actually cause quite a bit of havoc! Consider a script with the following contents:

<script type="text/javascript">// <
    while (true) { alert('hello!'); };
</script>

This will not only endlessly annoy your users, but it’ll actually lock up the page and prevent the rest of the DOM from loading! (Hilariously, some of Google’s internal endpoints actual prepend something similar to their results to prevent non-Google usage.) If the script isn’t malicious, it can return data to the page, using the same scope that any other script uses:

<script type="text/javascript" src="//api.appneta.com/some/endpoint?range=week">
// Returns a script with this
var apiDataResults = { data: ..., success: 'ok' };

This is similar to how tracking pixels work: by dynamically generating the URL, we can ask the server for data that’s unique to a single user, then dump it into Javascript’s scope. The only problem with this is that global variable is pretty ugly, and non-deterministic, as well. How do we know when that data is available? What if we call it multiple times, to update it? In true Javascript style, let’s wrap it in a callback:

<script src="//api.appneta.com/some/endpoint?range=week&amp;cb=myCallback">
// Returns a script with this
myCallback({ data: ..., success: 'ok' });

Now we’ve added an extra parameter to the API call: cb=myCallback. The API server takes this name and wraps the result in a call to that function. When the script returns, we get immediate execution, in the contex of our choice, and no ugly globals. This does require the cooperation of the API provider; you can’t force a call to wrap the results in a function unless they explicitly support it. Fortunately, many APIs do. The approach is called padded JSON, or JSONP, and can be used to build an entirely HTML / JS / CSS site that still pulls customized data for users from 3rd-party APIs like Intstagram or FourSquaare.

Form Posts

Form posts can also be used to offload data and processing to an external API. Most form submissions go to domain they were served from, but it’s entirely possible to submit that post arbitrarily. Once that form has been processed, the standard return value is a redirect to a “success!” page, but again, that result can be arbitrary. Many payments providers (like Recurly) will use this in the following sequence:

  • Webpage generates a credit card form
  • User fills out the form
  • User hits submit, which submits credit card information to Recurly
  • Recurly validates the information, and redirects the user to a URL with a unique token
  • Webpage uses that unique token to look up and validate the transaction
  • Webpage displays “success!” to the user, and considers the user paid up

Complicated? Definitely. But the advantage is that the user’s credit card information is never send to the webpage that asked for it. This dramatically reduces the security risks for the user and the webpage (you!), as well as simplifying the management of all of the payment details. This pattern does require a bit of server-side API integration as well, but not everything can be pure client-side, especially when there are payment details involved.

Measurements

Web apps are only getting more complex, and this list is by no means exhaustive. In particular, measuring the effect of these can be tricky, as they appear at different times in the page lifecycle, and (as with all 3rd party integrations) the performance of external services varies wildly depending on the time of day and the state of the service. As on the server side, web APIs can be used to both enrich the user’s experience and speed up development times. Be sure to keep track of the speed and correctness of every external API on your pages. Tools like Selenium can help; by scripting transactions that include all parts of the page, you can know which APIs are slow, and which ones break.

Are there other client-side API technologies you’ve used? Drop us a line, and let us know!