Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current View Version History

Version 1 Next »

<?xml version="1.0" encoding="utf-8"?>
<html>

Briefing

If you've ever looked at Firebug when loading a profile page, you're familiar with the flurry of activity involved in loading an OpenSocial app—but this doesn't mean your app can't be snappy to load. This field manual contains tactics for reducing latency in your app that will decrease the load on your servers and provide a better experience for your users.

Be sure to read the OpenSocial Latency Measurement guide to get an accurate measurement of your app's latency.

Best practices for web development

Many techniques that are used in normal web development will also benefit your OpenSocial app. Here are some of the most effective techniques.

Control the caching on your content

Most containers offer support for the

Cache-Control

HTTP header. You have server-side control over how your resources are cached, so be sure to set your headers appropriately for maximum benefit.

The

Cache-Control

header is best described in the HTTP/1.1 specification but there are some simpler descriptions available as well. If you're not sure about the cache headers your server is currently sending, you can try some publicly available tools to examine the cache headers on your files and see if they need to be tweaked.

Be aware that the

Cache-Control

header will be examined for all content coming from your server, including XML application specs, responses from

makeRequest

(both prefetched and not), and proxied images. Be sure to set caching headers for all of this content!

Notes on Apache

Apache defaults to using

Last-Modified

and

ETag

headers to control caching for static files, rather than the recommended

Expires

and

Cache-Control: max-age

headers. If you are using Apache, change your cache headers to

Expires

and

Cache-Control: max-age

!

Need to disable caching on your Apache server? Use the following in your

.htaccess

file to disable caching on

.css

,

.js

, and

.xml

files (change the

FilesMatch

line if you need to support more filetypes):

<source lang="javascript">
<FilesMatch "\.(css|js|xml)$">
Header unset ETag
FileETag None
Header set Cache-Control "no-cache"
</FilesMatch>
</source>

What are the benefits? Your server has much more control over how the container caches its content. You can set a low cache expiration for content that changes often, and a high cache timeout for content that does not change. Caching will become much more efficient once you set the appropriate headers.

Reducing the number of fetches

The HTTP/1.1 specification states:

<blockquote>Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.</blockquote>

For this reason, some internet browsers (like IE7) will only download two files from a given server at a time, shared amongst all HTML, XML, image, CSS, and JavaScript files. To reduce the number of connections that a user has to make back to your server, consolidate and inline as much code as possible.

If your JavaScript includes look like:

<source lang="javascript">

<script src="http://www.example.com/javascript.core.js" type="text/javascript"></script>
<script src="http://www.example.com/javascript.extra.js" type="text/javascript"></script>

</source>

then you should combine each file into one master JavaScript file:

<source lang="javascript">
<script src="http://www.example.com/javascript.all.js" type="text/javascript"></script>
</source>

Better yet, inline your code if at all possible:

<source lang="javascript">
<script type="text/javascript">

function1() {
...
};
...

</script>
</source>

This will save server connections for other assets. Remember that this approach can be used for CSS, as well.

To decrease the number of image files your application needs to load, you can use image spriting to combine all your image files into a single master "sprite" file. Check out A List Apart's CSS Spriting article for a good description of this technique.

Generally speaking, concatenating your files is a great performance improvement you can make. Because of the aggressive caching that containers perform, even using a relatively slow server-side script to automatically concatenate files will still wind up performing better than separate files (once the automatically concatenated file is cached). Aim for a single CSS and a single JS file in production.

What are the benefits? This approach keeps the number of server connections low, and reduces the total number of HTTP requests that each user of your application has to make.

Use Google's AJAX Libraries API for Popular JavaScript Frameworks

App developers very often use frameworks such as Prototype, jQuery, Dojo, or Script.aculo.us. In OpenSocial container environment, where applications are embedded in multiple iframes, these libraries often get loaded multiple times, not properly gzipped or minified. Google's latest AJAX libraries API addresses these issues by serving properly gzipped and minified version of these libraries from fast edge servers that are close to clients, providing caching once and for all, and significantly improving performance.

For example, to load Prototype 1.6.0.2 you would do the following:

<source lang="javascript">
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"></script>
</source>

For more info on how to take advantage of AJAX Libraries API to speed up your apps on Google's infrastructure, go to AJAX Libraries API.

Some other best practices:

*Turn on gzip for any content you deliver. Good things come in small packages.
*Minimize JS and CSS. Again, small is good.
*Split CSS and image files across 2-4 servers. Browsers limit the number of concurrent connections to any one server.
*Place JavaScript as late in the page as possible. Loading JavaScript blocks the downloading of other important components like images and CSS.

*Tip: *Try the YSlow Firefox plugin to analyze your app's performance.

Let the container cache your dynamic content

The

gadgets.io.getProxyUrl

function will return the location of the cached version of the URL you provide, including images, JavaScript, and CSS. So instead of using the URL of content hosted on your server, like this:

<source lang="javascript">
function showImage() {

imgUrl = 'http://www.example.com/i_heart_apis_sm.png';
html = '_img src="', imgUrl, '"_';
document.getElementById('dom_handle').innerHTML = html.join('');

};

showImage();
</source>

you can use the URL of the cached content, like this:

<source lang="javascript">
function showImage() {

imgUrl = 'http://www.example.com/i_heart_apis_sm.png';
cachedUrl = gadgets.io.getProxyUrl(imgUrl);
html = '_img src="', *cachedUrl*, '"_';
document.getElementById('dom_handle').innerHTML = html.join('');

};

showImage();
</source>

Use multiple content sections

Take advantage of multiple content sections in your gadget spec to render more tailored views for canvas and profile pages. This will help ensure that the container only loads the necessary components for each view. In particular, focus on making your profile view as lean as possible.

Use appData as a cache for content

It's much faster to request data from the container than it is to hit your own server. There are lots of ways you can cache your application data in the Persistence API and speed up page loads. The profile view is a great place to do this because it gets a lot of page views and there is less dynamic content.

Here's the slow way to load a profile page:

  1. User opens profile page.
  2. Your app uses
    makeRequest
    to get data from your server.
  3. Once the data is returned, your app renders the profile page.

Here's a much faster way:

  1. User opens profile page.
  2. Your app uses a
    DataRequest
    to get data from the container.
  3. Once the data is returned, your app renders the profile page.
  4. Now, your app uses
    makeRequest
    to get data from your server.
  5. Once the data is returned, your app updates the profile page.

An example

First, let's look at using multiple content sections. Here's the bare minimum:

<source lang="javascript">
<?xml version="1.0" encoding="UTF-8" ?>
<Module>

<ModulePrefs title="users &lt;3 speed">
<Require feature="opensocial-0.7" />
</ModulePrefs>
<Content type="html" view="profile">
<![CDATA[
Hello, profile!
]]>
</Content>
<Content type="html" view="canvas">
<![CDATA[
Hello, canvas!
]]>
</Content>

</Module>
</source>

Now let's use the technique where we populate the profile view with HTML cached in appData:

<source lang="javascript">

<Content type="html" view="profile">
<![CDATA[

<script type="text/javascript">

function request() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.OWNER), "owner");
req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.OWNER, "profile"), "usrdata");
req.send(response);
};

function response(data) {

console.log(data);
var usrdata = data.get("usrdata").getData(),
owner = data.get("owner").getData(),
profileHtml = 'No data';
if (usrdataowner.getId()) {
profileHtml = usrdataowner.getId().profile || 'Empty data';
}
document.write(profileHtml);

};

gadgets.util.registerOnLoadHandler(request);

</script>

]]>
</Content>

</source>

Finally, implement some functionality for the canvas view. When the user takes an action that will update the data shown in their profile, update the 'profile' field in appData. This app lets the user set a quote to be displayed on their profile. When the 'save' link is clicked, the quote and the HTML to display in the profile view are updated in appData. Here's the full application spec:

<source lang="javascript">
<?xml version="1.0" encoding="UTF-8" ?>
<Module>

<ModulePrefs title="users &lt;3 speed">
<Require feature="opensocial-0.7" />
</ModulePrefs>
<Content type="html" view="profile">
<![CDATA[

<script type="text/javascript">

function request() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.OWNER), "owner");
req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.OWNER, "profile"), "usrdata");
req.send(response);
};

function response(data) {

console.log(data);
var usrdata = data.get("usrdata").getData(),
owner = data.get("owner").getData(),
profileHtml = 'No data';
if (usrdataowner.getId()) {
profileHtml = usrdataowner.getId().profile || 'Empty data';
}
document.write(profileHtml);

};

gadgets.util.registerOnLoadHandler(request);

</script>

]]>
</Content>
<Content type="html" view="canvas">
<![CDATA[

<script type="text/javascript">

function request() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.OWNER), "owner");
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), "viewer");
req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.OWNER, "quote"), "appData");
req.send(response);
};

function response(data) {

var viewer = data.get("viewer") && data.get("viewer").getData(),
owner = data.get("owner") && data.get("owner").getData(),
appData = data.get("appData") && data.get("appData").getData(),
quote = '',
text = '';
if ((viewer.getId() || -1) == (owner.getId() || -2)) {
if (appDataowner.getId()) {
quote = appDataowner.getId();
}
text = ['Edit your quote: ',
'<input id="quote_input" type="text"/> ',
'<a href="javascript:void(0);" onclick="save();" value="',
quote,
'">save</a>'].join('');
document.getElementById('main').innerHTML = text;
}
};

function save() {
var quote = document.getElementById('quote_input').value,
profileHtml = '';
profileHtml = ', quote.join('');
req = opensocial.newDataRequest();
req.add(req.newUpdatePersonAppDataRequest(
opensocial.DataRequest.PersonId.VIEWER, "quote", quote), "updatequote");
req.add(req.newUpdatePersonAppDataRequest(
opensocial.DataRequest.PersonId.VIEWER, "profile", profileHtml), "updateprofile");
req.send(response2);
};

function response2(data) {
if (!data.hadError()) {
document.getElementById("status").innerHTML = "Saved quote at " + new Date();
} else {
document.getElementById("status").innerHTML = "There was a problem updating your profile";
}

/*

  • Now that the page is loaded you can use makeRequest to
  • see if you have fresher data on your server.
    */
    };

function status(text) {

var dom =
dom.innerHTML = text;
};

gadgets.util.registerOnLoadHandler(request);

</script>
<div id="main"></div>
<div id="status"></div>

]]>
</Content>

</Module>
</source>

</html>

  • No labels