Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

<?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">

<script
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

...

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

...

  1. User opens profile page.
  2. Your app uses a Code BlockDataRequest to get data from the container.
  3. Once the data is returned, your app renders the profile page.
  4. Now, your app uses Code BlockmakeRequest to get data from your server.
  5. 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 &amp;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
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 &amp;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") &amp;&amp; data.get("viewer").getData(),

owner =

        owner = data.get("owner") &amp;&amp; data.get("owner").getData(),

appData =

        appData = data.get("appData") &amp;&amp; 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
  • Now that the page is loaded you can use makeRequest to
  • see if you have fresher data on your server.
    */
    };

function status(text) {

Panel

var dom =
dom.innerHTML = text;
};


    }

    /*
     * 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>

...