<?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.
...
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
Code Block |
---|
<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.
...
The HTTP/1.1 specification states:
Code Block |
---|
...
*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. |
...
*
|
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">
Panel |
---|
Code Block |
<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 then you should combine each file into one master JavaScript file:<source lang="javascript">
<script
Code Block |
---|
<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
Code Block |
---|
<script type="text/javascript"> |
...
Panel |
---|
function1(){ { ... }; ... </script> |
</source>This This will save server connections for other assets. Remember that this approach can be used for CSS, as well.
...
For example, to load Prototype 1.6.0.2 you would do the following:<source lang="javascript">
<script
Code Block |
---|
<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
Code Block |
---|
function showImage() |
...
Panel |
---|
imgUrl = { imgUrl = 'http://www.example.com/i_heart_apis_sm.png'; html = ['_img src="', imgUrl, '"_']; document.getElementById('dom_handle').innerHTML = html.join(''); }; |
...
showImage(); |
...
|
you can use the URL of the cached content, like this:<source lang="javascript">
function
Code Block |
---|
function showImage() |
...
Panel |
---|
imgUrl = { imgUrl = 'http://www.example.com/i_heart_apis_sm.png'; cachedUrl = '''cachedUrl = gadgets.io.getProxyUrl(imgUrl); html = ''' html = ['_img src="',*cachedUrl*, '''cachedUrl''', '"_']; document.getElementById('dom_handle').innerHTML = html.join(''); }; |
...
showImage(); |
...
|
Use multiple content sections
...
- User opens profile page.
- Your app uses
Code Block makeRequest
to get data from your server. - Once the data is returned, your app renders the profile page.
...
- User opens profile page.
- Your app uses a
Code Block DataRequest
to get data from the container. - Once the data is returned, your app renders the profile page.
- Now, your app uses
Code Block makeRequest
to get data from your server. - Once the data is returned, your app updates the profile page.
...
First, let's look at using multiple content sections. Here's the bare minimum:<source lang="javascript">
Code Block |
---|
<?xml version="1.0" encoding="UTF-8" ?> |
...
Panel |
---|
<ModulePrefs <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">
Panel |
---|
Code Block |
<Content type="html" view="profile"> <![CDATA[ <script type="text/javascript"> |
...
Panel |
---|
function 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) |
...
Panel |
---|
{ console.log(data); var usrdata = data.get("usrdata").getData(), owner = owner = data.get("owner").getData(), profileHtml = profileHtml = 'No data'; if (usrdata[owner.getId()]){ profileHtml = usrdata { profileHtml = usrdata[owner.getId()].profile || 'Empty data'; } } document.write(profileHtml); }; |
...
Panel |
---|
gadgets.util.registerOnLoadHandler(request); </script> |
...
Panel |
---|
]]> </Content> |
...
|
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">
Code Block |
---|
<?xml version="1.0" encoding="UTF-8" ?> |
...
Panel |
---|
<ModulePrefs <Module> <ModulePrefs title="users &lt;3 speed"> <Require feature="opensocial-0.7" /> </ModulePrefs> <Content type="html" view="profile"> <![CDATA[ <script type="text/javascript"> |
...
Panel |
---|
function 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) |
...
Panel |
---|
{ console.log(data); var usrdata = data.get("usrdata").getData(), owner = owner = data.get("owner").getData(), profileHtml = profileHtml = 'No data'; if (usrdata[owner.getId()]){ profileHtml = usrdata { profileHtml = usrdata[owner.getId()].profile || 'Empty data'; } } document.write(profileHtml); }; |
...
Panel |
---|
gadgets.util.registerOnLoadHandler(request); </script> |
...
Panel |
---|
]]> </Content> <Content type="html" view="canvas"> <![CDATA[ <script type="text/javascript"> |
...
Panel |
---|
function 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) |
...
Panel |
---|
var viewer = { var viewer = data.get("viewer") && data.get("viewer").getData(), owner = owner = data.get("owner") && data.get("owner").getData(), appData = appData = data.get("appData") && data.get("appData").getData(), quote = quote = '', text = text = ''; if ((viewer.getId() || -1) == (owner.getId() || -2)){ if { if (appData[owner.getId()]){ quote = appData { quote = appData[owner.getId(); } text = ]; } text = ['Edit your quote: ', '<input id="quote_input" type="text"/> ', '<a href="javascript:void(0);" onclick="save();" value="', quote, '" quote, '">save</a>'].join(''); document.getElementById('main').innerHTML= text; } }; function save() { var quote = = text; } }; function save() { var quote = document.getElementById('quote_input').value, profileHtml = profileHtml = ''; profileHtml = ['Latest quote: ', quote].join(''); req = ); 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); }; |
Panel |
Panel |
function 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"; } |
/*
Panel |
---|
|
function status(text) {
Panel |
---|
var dom = |
} /* * 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> |
...
Panel |
---|
]]> </Content> </Module> |
...
|