100% found this document useful (2 votes)
52 views

Building An Opensocial App With Google App Engine

This document provides instructions for building an OpenSocial app that uses Google App Engine to store and access application data. It describes the key components of the app: 1) A Google App Engine project that handles requests and responses, 2) A database model to store gift and transaction data, 3) An admin interface to manage the data, 4) A JSON API to access the data, and 5) An OpenSocial app spec that interacts with the API. It then provides detailed steps to set up the Google App Engine project and implement each component to create a full-stack gift exchange app.

Uploaded by

John
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
52 views

Building An Opensocial App With Google App Engine

This document provides instructions for building an OpenSocial app that uses Google App Engine to store and access application data. It describes the key components of the app: 1) A Google App Engine project that handles requests and responses, 2) A database model to store gift and transaction data, 3) An admin interface to manage the data, 4) A JSON API to access the data, and 5) An OpenSocial app spec that interacts with the API. It then provides detailed steps to set up the Google App Engine project and implement each component to create a full-stack gift exchange app.

Uploaded by

John
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Building an OpenSocial App with Google App


Engine
From OpenSocial

Lane LiaBraaten, Google Developer Programs\ September 2008

While you can write OpenSocial apps that run solely in JavaScript and use the Persistence API to store data on
the container, many OpenSocial apps communicate with a third-party server for data storage or application
logic. Integrating with your own third-party server allows you to add new dimensions to your app, like providing
a data API, hosting static content, or allowing configuration through an admin console.

In this article, we'll build an app that is similar to the gift-giving application built in the OpenSocial tutorial
(http://code.google.com/apis/opensocial/articles/tutorial/) . When a user views the app, they see a drop-down
menu of gifts (such as a peanut, or a red pistachio nut) and another drop-down menu containing a list of their
friends. The user can give any of these gifts to a friend and the gift transaction will be displayed. The app will
also display any gifts that the user has received. You can find all the source code used to run this application in
the opensocial-gifts (http://code.google.com/p/opensocial-gifts/) project on Google Code Project Hosting. You
can also install this app (http://sandbox.orkut.com/Application.aspx?appId=571657112201) on the orkut
sandbox.

The original gift-giving app is built using 100% client-side OpenSocial code and is therefore subject to a number
of limitations imposed by the container rendering the app, such as the amount of data the container will let you
store, and the access controls related to when you can read and write data. With Google App Engine, you can
manage all this data on an external server, freeing your app from any constraints imposed by the container. Viva
la revolución!

Contents
1 Audience
2 Architecture
2.1 Google App Engine app (app.yaml and gifts.py)
2.2 Database model (db_model.py)
2.3 Admin interface (admin.py)
2.4 JSON data API (api.py)
2.5 OpenSocial application spec (gifts.xml)
3 Setting up a Google App Engine app
4 Using Google App Engine to store data
4.1 Defining the data model
4.2 Populating the datastore
4.3 Accessing the datastore
5 A simple Google App Engine web interface
5.1 Creating a request handler
5.2 Forwarding requests
5.3 Identifying the user

1 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

5.4 Invoking the Admin class


6 Creating a simple data API with Google App Engine
6.1 Requesting Gifts
6.2 Requesting GiftTransactions
6.3 Recording GiftTransactions
6.4 API Reference
7 Serving static files with Google App Engine
8 Communication between OpenSocial and Google App Engine
8.1 Publishing your app
8.2 Requesting friends
8.3 Requesting gifts
8.4 Requesting gift transactions
8.5 Recording new gift transactions
8.6 Sending and verifying signed requests
9 Next Steps
10 Resources
10.1 Developer Forums
10.2 Reference

Audience
The ideal audience for this article knows a little about Google App Engine, and a little about OpenSocial. We'll
build this app from ground up, but it will help if you've worked through the Google App Engine Getting Started
Guide (http://code.google.com/appengine/docs/gettingstarted/) and the Opensocial Tutorial
(http://code.google.com//apis/opensocial/articles/tutorial/) .

OpenSocial apps are written in JavaScript and HTML, so you'll need to be able to read these languages to
understand the examples. You'll also need to be familiar with Python because that's what all the server side
Google App Engine code is written in. That said, we're not doing anything too complicated here, so if you've got
some basic web development experience, you should be fine.

Note: You'll need a Google account and the App Engine SDK (http://code.google.com/p/googleappengine) to
start coding.

Architecture
By the end of this article, you'll have constructed an OpenSocial app where user's can give each other gifts.
Along the way, we'll implement components to store, access, manipulate, and display application data (gifts and
gift transactions). As you implement more functional apps, the implementations for each of these components
will likely be more complex, but the general interactions and components themselves can remain mostly the
same. This gift-giving app has five such components (in order of appearance):

1. Google App Engine app


2. Database model
3. Admin interface
4. JSON data API
5. OpenSocial application spec

2 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Total servers you need to maintain: 0.

Google App Engine app (app.yaml (http://opensocial-gifts.googlecode.com/svn/trunk


/app.yaml) and gifts.py (http://opensocial-gifts.googlecode.com/svn/trunk/gifts.py) )

Google App Engine projects are defined and configured using an app.yaml file that defines how to handle
incoming requests. In this file, we'll configure our app to send requests to gifts.py where we'll create a
WSGIApplication that will handle these requests and send appropriate responses.

Database model (db_model.py (http://opensocial-gifts.googlecode.com/svn/trunk


/db_model.py) )

The data for this app will be stored using the Google App Engine datastore. We'll store the gifts that are
available for giving as a character string for now. When a user gives a gift we'll store the sender, receiver, and
gift given as a gift transaction. We can leverage the IDs provided by the OpenSocial API to identify the users
involved in a gift transaction.

Admin interface (admin.py (http://opensocial-gifts.googlecode.com/svn/trunk/admin.py) )

We'll use Google App Engine to host a small web application that will allow us to perform some simple
operations on the datastore. We'll use Google App Engine's authentication and identity management tools to
ensure that only certain users can access this console and manipulate data directly. Our admin interface will
allow us to initialize or view the data in the datastore, but you could extend this concept to enable more complex
management tasks.

Note: Google App Engine provides an Admin Console (http://appengine.google.com) where you can perform
many useful tasks, like manipulating data in the datastore, viewing application logs, and accessing metrics about
your app's usage.

JSON data API (api.py (http://opensocial-gifts.googlecode.com/svn/trunk/api.py) )

We'll expose the data in the datastore through a data API (also hosted by Google App Engine). The API will
accept HTTP GET requests to access gifts or gift transactions and return the data as a JSON string. The API will
also accept HTTP POST requests to add a gift transaction to the datastore.

OpenSocial application spec (gifts.xml (http://opensocial-gifts.googlecode.com/svn/trunk


/static/gifts.xml) )

Our users will be interacting with the OpenSocial app, defined in an application spec XML file. The application
spec contains HTML and JavaScript that will be rendered by an OpenSocial container, and the JavaScript will
run client-side in the user's browser. The OpenSocial app will make calls to the data API hosted on Google App
Engine using gadgets.io.makeRequest.

Setting up a Google App Engine app


First you need to decide on an application identifier for your application. This identifier must be unique across
all Google App Engine applications, so for this article, use opensocial-gifts-username where username is your
username (e.g. opensocial-gifts-johndoe). If you have a Google App Engine account, log into the Google App

3 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Engine admin console (http://appengine.google.com/) and create an app. Since App Engine is currently in a
preview release, you can only publish up to three applications for now (although you can create as many local
applications as you'd like). Bearing this in mind, you may want to use a generic application identifier, like
username-dev.

The first iteration of our application will have just two files: app.yaml and gifts.py. The app.yaml file
contains configuration details about your app, including which Python scripts to execute when your app receives
requests. Here's a simple app.yaml file to get us started:

application: opensocial-gifts-username
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
script: gifts.py

Note: The value used for application needs to uniquely identify your Google app Engine app so be sure to
replace username with your username (e.g. opensocial-gifts-jdoe).

In the app.yaml file, we specified that all requests should be handled by gifts.py using the regex /.*. Let's
define a WSGIApplication in gift.py and create a temporary RequestHandler so we can do a quick sanity
check. Here's the content in gifts.py:

# Standard libraries
import wsgiref.handlers

# AppEngine imports
from google.appengine.ext import webapp

class SanityCheck(webapp.RequestHandler):
def get(self):
self.response.out.write("You're not crazy!")

# Map URLs to request handler class


application = webapp.WSGIApplication([('/', SanityCheck)],
debug=True)

# Fire it up!
wsgiref.handlers.CGIHandler().run(application)

Now that we've got a simple app, we'll test it with the development web server (http://code.google.com
/appengine/docs/thedevwebserver.html) . If you haven't already, download the SDK (http://code.google.com
/p/googleappengine) and uncompress it. From the google_appengine directory, run './dev_appserver.py
<your_app_directory>;'. Verify that you can access your app from a browser (the URL will be
http://localhost:8080/).

Using Google App Engine to store data


The original gift-giving app uses the OpenSocial Persistence API to record the gifts that each user gives. Since
data can only be written for the VIEWER, the original app has to do some extra work to perform simple
operations. For example, to show all the gifts a person has received, the app iterates through all of their friends
to see which have given them a gift. Another potential issue with the Persistence API is that the storage limits

4 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

are set by the container. We'll circumvent both of these issues by using Google App Engine to store our data.

Defining the data model

We basically just need to store a history of who gave what gift to whom. For each gift, we can just store the
name of the gift. For each gift transaction, we need to store the sender, recipient, and a reference to the gift that
was given. We can use the OpenSocial user IDs to identify the sender and recipient, and we can use the
db.Key() object for the reference to the gift object.

To define this model in the Google App Engine datastore, create a file called db_model.py and insert the
following Python code:

from google.appengine.ext import db

class Gift(db.Model):
name = db.StringProperty()

class GiftTransaction(db.Model):
sender_id = db.StringProperty()
receiver_id = db.StringProperty()
gift = db.ReferenceProperty(Gift)

This defines two Model classes, one to represent a gift and one to represent a gift transaction where a sender
gives a recipient a gift.

Populating the datastore

Now that we have a data model, we need some way to populate it. Create a file called admin.py and define an
Admin class to handle administrative operations like this. Let's start with two methods for initializing the gifts
and gift transactions in the datastore:

from db_model import Gift, GiftTransaction

class Admin:
"""Initializes the list of gifts in the datastore."""

def initGifts(self):
"""Deletes any existing gifts and add the default gifts."""
for gift in Gift.all():
gift.delete()
GIFT_NAMES = ['a cashew nut',
'a peanut',
'a hazelnut',
'a red pistachio nut']
for name in GIFT_NAMES:
gift = Gift()
gift.name = name
gift.put()

def initGiftTransactions(self):
"""Deletes any existing gift transactions."""
for t in GiftTransaction.all():
t.delete()

Accessing the datastore

5 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Now let's add a couple methods to the Admin class for accessing the gifts and gift transactions in the datastore.

def getGiftNames(self):
names = []
for gift in Gift.all():
names.append(gift.name)
return names

def getGiftTransactions(self):
giftTransactions = []
for t in GiftTransaction.all():
giftTransactions.append("sender: %s, reciever: %s, gift: %s" %
(t.sender_id, t.receiver_id, t.gift.key()))
return giftTransactions

Great, now we can read and write data in the datastore—but how do we invoke this Python code? That's where
the admin webapp comes into play.

A simple Google App Engine web interface


Now we'll extend our Google App Engine application to include an admin web application so we can initialize or
view the data in the data store from a browser. We'll create a request handler so that we can invoke the Admin
class by sending a GET request to a certain URL, like http://opensocial-gifts-username.appspot.com
/admin?action=init.

Creating a request handler

The AdminServer class will be a subclass of the RequestHandler class provided by the
google.appengine.ext.webapp package. We can implement a get method that will be invoked any time the
application forwards a request to this class. Add the following import statement and class definition to
admin.py:

import google.appengine.ext.webapp

class AdminServer(webapp.RequestHandler):
"""Handles requests to /admin URLs and delegates to the Admin class."""

def get(self):
"""Handle GET requests."""
self.response.out.write('Welcome to the admin webapp')

Forwarding requests

First, let's get rid of the SanityCheck class we started out with in gifts.py. Instead, import the admin module
so we can access the AdminServer class. We'll edit the WSGIApplication constructor to forward requests for
"/admin" URLs to the AdminServer class. Here's an updated version of the gifts.py file with changes bolded:

6 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

# Standard libraries
import wsgiref.handlers

# AppEngine imports
from google.appengine.ext import webapp

'''# OpenSocial Gifts imports


import admin'''

# Map URLs to request handler classes


application = webapp.WSGIApplication('''[('/admin', admin.AdminServer)]''',
debug=True)

# Fire it up!
wsgiref.handlers.CGIHandler().run(application)

Now hit the URL http://localhost:8080/admin with your browser. You should see the admin web
application welcome message.

Identifying the user

One of the coolest features of App Engine is that it handles authentication and identity management for you. We
only need a few lines of code to allow for user logins and we can differentiate between regular users and
administrators with the users.IsCurrentUserAdmin method. We'll leverage this in the get method to make
sure normal users can't access our admin console:

from google.appengine.api import users

def get(self):
"""Ensure that the user is an admin."""
if not users.GetCurrentUser():
loginUrl = users.CreateLoginURL(self.request.uri)
self.response.out.write('<a href="%s">;Login</a>;' % loginUrl)
return

if not users.IsCurrentUserAdmin():
self.response.out.write('You must be an admin to view this page.')
return

self._handleRequest()

def _handleRequest(self):
self.response.out.write('Welcome to the admin web interface')

Browse to the admin console again and you'll see a "Login" link. If you login as a user that's not an administrator
of the Google App Engine app, you'll see a message stating that "You must be an admin to view this page."

Invoking the Admin class

Now we can add some code to invoke the Admin class by implementing the _handleRequest method from the
previous code snippet. The following method searches the request URL for a parameter called 'action' and,
based on this value, either initializes the datastore or lists the gifts and gift transactions.

7 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

def _handleRequest(self):
"""Invokes methods from the Admin class based on the 'action' parameter"""
admin = Admin()
action = self.request.get('action')
if action == 'init':
admin.initGifts()
admin.initGiftTransactions()
msg = "Gifts have been initialized, gift transactions have been cleared."
self.response.out.write(msg)
elif action == 'list':
self.response.out.write("Gifts = %s" % admin.getGiftNames())
self.response.out.write("<br>;")
self.response.out.write("Gift Transactions = %s" % admin.getGiftTransactions())
else:
html = []
html.append('<a href="/admin?action=init">;Initialize datastore</a>;<br>;')
html.append('<a href="/admin?action=list">;List all data in datastore</a>;')
self.response.out.write(''.join(html))

Note that if no 'action' parameter is given (or if the value is not 'init' or 'list') the handler will display links to
initialize the datastore or list the gift data.

Creating a simple data API with Google App Engine


Requesting Gifts

Let's start by creating a request handler that will return the list of gifts in a JSON format. If a request comes in to
http://opensocial-gifts-username.appspot.com/gifts, we should return:

["a cashew nut", "a peanut", "a hazelnut", "a red pistachio nut"]

Create a file called api.py to contain the API request handler. Implement the get method to return the list of
gifts as a JSON string.

# App Engine imports


from google.appengine.ext import webapp

# Third party imports


import json

# OpenSocial Gifts imports


from db_model import Gift, GiftTransaction

class ApiServer(webapp.RequestHandler):
"""Handles requests to /gifts URLs and reponds with JSON strings."""

def get(self):
"""Respond with a JSON string representation of the lists of gifts."""
gifts = []
for gift in Gift.all():
item = {'key' : str(gift.key()),
'name' : gift.name }
gifts.append(item)
self.response.out.write(json.write(gifts))

Note: The ApiServer class uses the python-json (http://pypi.python.org/pypi/python-json/) package. The
json.py file is included in the application directory with the rest of the source code. Thanks, Patrick Dlogan!

8 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Now update the application in gifts.py to forward requests to the '/gifts' path to this new API request handler.

import admin
import api

# Map URLs to request handler class


application = webapp.WSGIApplication([('/admin', admin.AdminServer),
'''('/gifts', api.ApiServer)'''],
debug=True)

Requesting GiftTransactions

We also want to expose GiftTransactions through this api, so let's add another value to the WGSIApplication
constructor in gifts.py:

# Map URLs to request handler class


application = webapp.WSGIApplication([('/admin', admin.AdminServer),
('/gifts', api.ApiServer),
'''('/giftTransactions', api.ApiServer)'''],
debug=True)

Now we have two types of GET requests coming into the ApiServer and we can differentiate them based on
the URL path:

def get(self):
"""Call the appropriate handler based on the path of the HTTP request."""
if self.request.path.beginsWith('gifts'):
self._handleGifts()
elif self.request.path.beginsWith('giftTransactions'):
self._handleGiftTransactions()

def _handleGifts(self):
gifts = []
for gift in Gift.all():
item = {'key' : str(gift.key()),
'name' : gift.name }
gifts.append(item)
self.response.out.write(json.write(gifts))

def _handleGiftTransactions(self):
#TODO(you) return a list of GiftTransactions as JSON

In the last snippet, the code for returning the list of gifts was moved into the _handleGifts section. Now we
need to implement the _handleGiftTransactions method.

We should expect requests for two lists of GiftTransactions: a list of gifts a user has sent and a list of gifts a
user has received. Let's design the API to accept the sender or receiver ID as a URL parameter and determine
the list of GiftTransactions to return based on the values of these parameters.

9 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

def _returnGiftTransactions(self):
"""Return the list of transactions specified by the URL query parameters."""
sender_id = self.request.get("sender_id")
receiver_id = self.request.get("receiver_id")
giftTransactions = self._getGiftTransactions(sender_id, receiver_id)

results = []
for giftTransaction in giftTransactions:
item = { 'sender_id' : giftTransaction.sender_id,
'receiver_id' : giftTransaction.receiver_id,
'gift_name' : giftTransaction.gift.name }
results.append(item)
self.response.out.write(json.write(results))

def _getGiftTransactions(self, sender_id, receiver_id):


results = []
if sender_id:
results = GiftTransaction.gql('WHERE sender_id=:sender_id',
sender_id=sender_id)
elif receiver_id:
results = GiftTransaction.gql('WHERE receiver_id=:receiver_id',
receiver_id=receiver_id)
else:
results = GiftTransaction.all()

return results;

Recording GiftTransactions

The last feature we need to add to the data API is the ability to record a new GiftTransaction. These requests
will come in as HTTP POST requests to the /giftTransactions path with the sender ID, receiver ID, and gift key
included as POST data. To handle this request, we simply need to implement a post method in the ApiServer
class.

def post(self):
"""Store a new gift transaction in the datastore based on the POST data."""
giftTransaction = GiftTransaction()
giftTransaction.sender_id = self.request.get('sender_id')
giftTransaction.receiver_id = self.request.get('receiver_id')
giftTransaction.gift = Gift.get(self.request.get('gift_key')).key()
giftTransaction.put()

API Reference

Here's a summary of the API we just built:

HTTP
URL Description Example Response
Method
[{"name" : "a peanut",
"key" : "ABC"},
Returns the names and keys of all gifts
GET /gifts {"name" : "a
in the datastore as a JSON array.
cashew",
"key" : "XYZ"}]
Returns an array of gift transactions [{"sender_id":"yyyy",
GET /giftTransactions?receiver_id=xxxx where the receiver is specified by the "receiver_id":"xxxx",
URL parameter receiver_id. "gift_key":"XYZ"},

10 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

{"sender_id":"zzzz",
"receiver_id":"xxxx",
"gift_key":"ABC"}]
[{"sender_id":"xxxx",
receiver_id:"yyyy",
Returns an array of gift transactions
gift_key:"XYZ"},
GET /giftTransactions?sender_id=xxxx where the sender is specified by the
{"sender_id":"xxxx",
URL parameter sender_id.
receiver_id:"zzzz",
gift_key:"XYZ"}]
HTTP
URL Description Example POST data
Method
Creates a new gift transaction in the datastore sender_id=xxxx&
POST /giftTransactions based on the sender, receiver, and gift key receiver_id=yyyy&
specified in the POST data. gift_key=XYZ

Serving static files with Google App Engine


You can configure your app to serve up static content by editing the app.yaml file. Just specify the URL of the
static directory and the name of the directory that will contain static content. Here's a copy of the app.yaml file
with these changes bolded:

application: opensocial-gifts-'''''username'''''
version: 1
runtime: python
api_version: 1

handlers:

'''- url: /static


static_dir: static'''

- url: /.*
script: gifts.py

Note: Google App Engine caches static files for 10 minutes by default, but you can control this caching period
in the app.yaml file. You'll find the details in the Configuring an App (http://code.google.com/appengine
/docs/configuringanapp.html#Static_File_Handlers) documentation.

In your application directory, create a directory called static. This directory is where we'll keep our
OpenSocial app spec. Create a file called gifts.xml in the static directory and add the following content:

<?xml version="1.0" encoding="UTF-8"?>;


<Module>;
<ModulePrefs title="Gifts" />;
<Content type="html">;
<![CDATA[
Hello, Google App Engine!
]]>;
</Content>;
</Module>;

Browse to http://localhost:8080/static/gifts.xml and verify that you can see your OpenSocial

11 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

application spec.

Communication between OpenSocial and Google App Engine


Now that we have some data in the datastore and an API to access it, we can enhance our OpenSocial app by
enabling it to communicate with the Google App Engine project. We'll start by requesting the list of gifts and
friends and displaying them in drop-down menus. Then we'll request the gift transactions from the data API we
built. Finally, we'll let the user actually give a gift by POSTing a new gift transaction to the data API.

Publishing your app

Up to this point, we've been using the (local) development app server, but in order for an OpenSocial container
like orkut or MySpace to access your application spec, the file needs to be hosted publicly. From the
google_appengine directory, run &apos;./appcfg.py update <your_app_directory>;&apos; from the
application directory to publish your app. After the upload is complete, make sure you can access the
OpenSocial application spec XML file at http://opensocial-gifts-username/static/gifts.xml from your browser.

Requesting friends

The following application will request the list of friends from the OpenSocial container when the page loads and
display the friends in a drop-down menu.

12 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

<?xml version="1.0" encoding="UTF-8"?>;


<Module>;
<ModulePrefs title="Gifts" >;
'''<Require feature="opensocial-0.8"/>'''
</ModulePrefs>;
<Content type="html">;
<![CDATA[
<script>;
'''gadgets.util.registerOnLoadHandler(init);

function init() {
loadFriends();
}

function loadFriends() {
var req = opensocial.newDataRequest();

var viewerFriendsIdSpec = opensocial.newIdSpec({ "userId" : "VIEWER", "groupId" : "FRIENDS" });


var opt_params = {};
opt_params[opensocial.DataRequest.PeopleRequestFields.MAX] = 100;
req.add(req.newFetchPeopleRequest(viewerFriendsIdSpec, opt_params), 'viewerFriends');
req.send(onLoadFriends);
}

function onLoadFriends(data) {
var viewerFriends = data.get('viewerFriends').getData();

html = new Array();


html.push('<select id="person">;');
viewerFriends.each(function(person) {
html.push('<option value="' + person.getId() + '">;' + person.getDisplayName() + "</option>;")
});
html.push('</select>;');
document.getElementById('friends').innerHTML = html.join('');
}
</script>;
<span id="friends"></span>;'''
]]>;
</Content>;
</Module>;

The loadFriends method sends a DataRequest to the OpenSocial container and specifies the onLoadFriends
method as a callback function. The onLoadFriends method receives a DataResponse object that contains an
opensocial.Collection of opensocial.Person objects which represent the user's friends. The name of each
of these friends is then added as an option in the drop-down menu. The drop-down menu, a <select>; element,
is then inserted into the 'friends' span.

Requesting gifts

Next, let's add a drop-down menu for the list of gifts. We'll request the gift data, format it into a drop-down
menu, and add it to the HTML in a 'gifts' span. First, add the 'gifts' span at the end of the application spec:

13 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

<?xml version="1.0" encoding="UTF-8"?>;


<Module>;
<ModulePrefs title="Gifts" >;
<Require feature="opensocial-0.8"/>
</ModulePrefs>;
<Content type="html">;
<![CDATA[
<script>;
<-- all the JavaScript -->;
</script>;
'''Give <span id="gifts">;</span>; to <span id="friends">;</span>;.'''
]]>;
</Content>;
</Module>;

Next, create a loadGifts method and invoke it when the page loads.

function init() {
loadFriends();
'''loadGifts();'''
}

'''function loadGifts() {
var params = {};
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
var url = 'http://opensocial-gifts-'''''username'''''.appspot.com/gifts';

gadgets.io.makeRequest(url, onLoadGifts, params);


}'''

The loadGifts method uses the gadgets.io.MakeRequest method to send a request to the JSON data API for
the array of available gifts. Once we get the response, the callback method, onLoadGifts, will display the gifts
in a drop-down menu.

function onLoadGifts(response) {
var gifts = response.data;
var html = new Array();
html.push('<select id="nut">;');
for (var i = 0; i < gifts.length; i++) {
html.push('<option value="' + gifts[i].key + '">;' + gifts[i].name + '</option>;');
}
html.push('</select>;');
document.getElementById('gifts').innerHTML = html.join('');
}

Requesting gift transactions

Next we want to display the gifts the user has given and received. Since the data API only returns user IDs, we'll
need to build an object to map the IDs to the names of our friends (so the app can display names instead of user
IDs). We can build this object at the same time that we build the drop-down menu of the user's friends, then
pass it into a new method called loadGiftTransactions where we'll actually make the requests to the data
API running on the Google App Engine project. To do this, modify the onLoadFriends method so it looks like
this:

14 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

function onLoadFriends(data) {
var viewer = data.get('viewer').getData();
var viewerFriends = data.get('viewerFriends').getData();
'''var friends = new Array();'''

html = new Array();


html.push('<select id="person">;');
viewerFriends.each(function(person) {
html.push('<option value="' + person.getId() + '">;' + person.getDisplayName() + "</option>;")
'''friends[person.getId()] = person.getDisplayName();'''
});
html.push('</select>;');
document.getElementById('friends').innerHTML = html.join('');

'''loadGiftTransactions(viewer, friends);'''
}

In the loadGiftTransactions method, we'll construct the appropriate URLs to fetch this data from the data
API and use makeRequest to send the requests for gift transaction data. Each call to makeRequest specifies a
callback method, which is actually a closure so that we can use the friends object built in the onLoadFriends
method when processing the results of the data request. Here is the implementation for the
loadGiftTransactions method and the two callbacks:

15 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

function loadGiftTransactions(viewer, friends) {


// Get the gift transactions where the VIEWER is the sender
var url = 'http://opensocial-gifts-'''''username'''''/giftTransactions?sender_id=' + viewer.getI
gadgets.io.makeRequest(url, onLoadGiftsGivenClosure(friends));

// Get the gift transactions where the VIEWER is the receiver


var url = 'http://opensocial-gifts-'''''username'''''/giftTransactions?receiver_id=' + viewer.ge
gadgets.io.makeRequest(url, onLoadGiftsReceivedClosure(friends));
}

function onLoadGiftsGivenClosure(friends) {
return function(response) {
var giftTransactions = gadgets.json.parse(response.data);
var html = new Array();
html.push('You have given:');
html.push('<ul>');
for (var i=0; i<giftTransactions.length; i++) {
html.push('<li>' + friends[giftTransactions[i].receiver_id] + ' received ');
html.push(giftTransactions[i].gift_name + '</li>');
}
html.push('</ul>');
document.getElementById('given').innerHTML = html.join('');

gadgets.window.adjustHeight();
}
}

function onLoadGiftsReceivedClosure(friends) {
return function(response) {
var giftTransactions = gadgets.json.parse(response.data);
var html = new Array();
html.push('You have received:<ul>');
for (var i=0; i<giftTransactions.length; i++) {
html.push('<li>' + giftTransactions[i].gift_name + ' from ');
html.push(friends[giftTransactions[i].sender_id] + '</li>');
}
html.push('</ul>');
document.getElementById('received').innerHTML = html.join('');

gadgets.window.adjustHeight();
}
}

Since displaying these gift transactions may take up more height than the container provides by default, these
callbacks use gadgets.window.adjustHeight to resize the app after inserting the data into the page. In order
to use this new method, you need to include <Require feature="dynamic-height"/> in the <ModulePrefs>
element of the application spec.

Finally, add the 'given' and 'received' <div> elements to the HTML section of the application spec:

16 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

<?xml version="1.0" encoding="UTF-8"?>


<Module>
<ModulePrefs title="Gifts" >
<Require feature="opensocial-0.8"/>
'''<Require feature="dynamic-height"/>'''
</ModulePrefs>
<Content type="html">
<![CDATA[
<script>
<-- all the JavaScript -->
</script>
Give <span id="gifts"></span> to <span id="friends"></span>.
'''<div id="given"></div>
<div id="received"</div>'''
]]>
</Content>
</Module>

Recording new gift transactions

The last piece for functionality to add is the ability for a user to actually give a gift to their friend. To do this,
we'll add a "Give!" link to the HTML that will invoke a giveGift JavaScript function when clicked.

<?xml version="1.0" encoding="UTF-8"?>


<Module>
<ModulePrefs title="Gifts" >
<Require feature="opensocial-0.8"/>
<Require feature="dynamic-height"/>
</ModulePrefs>
<Content type="html">
<![CDATA[
<script>
<-- all the JavaScript -->
</script>
Give <span id="gifts"></span> to <span id="friends"></span>.
'''<a href="javascript:void(0);" onclick='giveGift();'>Give!</a>'''
<div id="given"></div>
<div id="received"</div>
]]>
</Content>
</Module>

Note: When using links to invoke JavaScript functions, always use href="javascript:void(0);". Using
href="#" causes unexpected results in the container.

The giftGive function just requests the VIEWER from the container and sends the receiver and gift key data
to the callback function for that request. In the callback, we make a POST request (using makeRequest) to the
data API that contains the sender and receiver IDs and the key of the gift given as post data.

17 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

function giveGift() {
var gift_key = document.getElementById('nut').value;
var receiver_id = document.getElementById('person').value;

var req = opensocial.newDataRequest();


req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER), 'viewer');
req.send(postGiftTransactionClosure(receiver_id, gift_key));
}

function postGiftTransactionClosure(receiver_id, gift_key) {


return function(response) {
var sender_id = response.get('viewer').getData().getId();
var params = {};
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
post_data = gadgets.io.encodeValues({
'sender_id' : sender_id,
'receiver_id' : receiver_id,
'gift_key' : gift_key });
params[gadgets.io.RequestParameters.POST_DATA] = post_data;
var url = http://opensocial-gifts-'''''username'''''/giftTransactions';

gadgets.io.makeRequest(url, loadFriends, params);


}
}

Notice that the callback function for this makeRequest call is loadFriends. This will basically redraw the app
after the gift transaction is processed.

Sending and verifying signed requests

You don't want just anybody to be able to access your application data through the data API. OpenSocial
containers can add digital signatures to the requests that go out to your servers, or in this case, Google App
Engine.

To send a signed request from your OpenSocial app, just change the parameters sent to makeRequest as follows:

function postGiftTransactionClosure(receiver_id, gift_key) {


return function(response) {
var sender_id = response.get('viewer').getData().getId();
var params = {};
'''params[gadgets.io.RequestParameters.AUTHORIZATION = gadgets.io.AuthorizationType.SIGNED;'''
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
post_data = gadgets.io.encodeValues({
'sender_id' : sender_id,
'receiver_id' : receiver_id,
'gift_key' : gift_key });
params[gadgets.io.RequestParameters.POST_DATA] = post_data;
var url = 'http://opensocial-gifts-'''''username'''''/giftTransactions';

gadgets.io.makeRequest(url, loadFriends, params);


}
}

Using signed requests is most important when you're executing actions on the user's behalf since you don't want
a malicious user performing actions for a legitimate user. For example, a malicious user could send POST
requests to the /giftTransactions URL of our data API and include any sender ID, receiver ID, or gift key.
By signing your requests, you can protect your data from unauthorized access—if a request is forged, you can
reply with an error message or nothing at all.

18 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

You will need to add code to the api.py class to verify the signature received from the container. We can
implement an _isValidSignature() method and call it before processing GET or POST requests:

'''def _isValidSignature(self):
return False'''

def get(self):
"""Respond with a JSON string representation of the lists of gifts."""
'''if not self._isValidSignature():
self.response.out.write(json.write({}))
return'''

if self.request.path.startswith('/gifts'):
self._returnGifts()
elif self.request.path.startswith('/giftTransactions'):
self._returnGiftTransactions()

def post(self):
"""Store a new gift transaction in the datastore based on the POST data."""
'''if not self._isValidSignature():
return'''

giftTransaction = GiftTransaction()
giftTransaction.sender_id = self.request.get('sender_id')
giftTransaction.receiver_id = self.request.get('receiver_id')
giftTransaction.gift = Gift.get(self.request.get('gift_key')).key()
giftTransaction.put()

OpenSocial uses OAuth's method for signing requests (http://oauth.net/core/1.0/#signing_process) and


containers may use the HMAC-SHA1 or RSA-SHA1 algorithms. The following sample code demonstrates the
RSA-SHA1 algorithm and assumes the container is orkut. Orkut's public key is available in an x509 certificate
(http://sandbox.orkut.com/46/o/pub.1199819524.-1556113204990931254.cer) , which has been parsed,
converted to hex value, and hard-coded in the public_key_str variable.

19 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

import base64
import hashlib
import urllib

import oauth
from Crypto.PublicKey import RSA
from Crypto.Util import number

def _isValidSignature(self):

# Construct a RSA.pubkey object


exponent = 65537
public_key_str = """0x\
00b1e057678343866db89d7dec2518\
99261bf2f5e0d95f5d868f81d600c9\
a101c9e6da20606290228308551ed3\
acf9921421dcd01ef1de35dd3275cd\
4983c7be0be325ce8dfc3af6860f7a\
b0bf32742cd9fb2fcd1cd1756bbc40\
0b743f73acefb45d26694caf4f26b9\
765b9f65665245524de957e8c547c3\
58781fdfb68ec056d1"""
public_key_long = long(public_key_str, 16)
public_key = RSA.construct((public_key_long, exponent))

# Rebuild the message hash locally


oauth_request = oauth.OAuthRequest(http_method=self.request.method,
http_url=self.request.url,
parameters=self.request.params.mixed())
message = '&amp;'.join((oauth.escape(oauth_request.get_normalized_http_method()),
oauth.escape(oauth_request.get_normalized_http_url()),
oauth.escape(oauth_request.get_normalized_parameters()),))
local_hash = hashlib.sha1(message).digest()

# Apply the public key to the signature from the remote host
sig = base64.decodestring(urllib.unquote(self.request.params.mixed()["oauth_signature"]))
remote_hash = public_key.encrypt(sig, '')[0][-20:]

# Verify that the locally-built value matches the value from the remote server.
if local_hash==remote_hash:
return True
else:
return False

The _isValidSignature method uses two third party modules. OAuth's Python client library
(http://oauth.googlecode.com/svn/code/python/oauth/) was written by Leah Culver and the RSA code is just a
small piece of the pycrypto (http://www.amk.ca/python/code/crypto.html) toolkit. Score one for open source!

In case you didn't cut and paste everything just right, this application is hosted in the opensocial-gifts
(http://code.google.com/p/opensocial-gifts/source/browse) Google Code project where you can find the
OpenSocial application spec (http://code.google.com/p/opensocial-gifts/source/browse/trunk/static/gifts.xml) ,
the full implementation of the JSON data API (http://code.google.com/p/opensocial-gifts/source/browse/trunk
/api.py) , and all the other files used in this application.

Next Steps
This tutorial has only scratched the surface of what you can do with Google App Engine and OpenSocial. Try
adding some of these features to your app:

Display pictures for each gift instead of text (Hint: store the images in your static directory).

20 of 21 4/12/2009 11:56 PM
Building an OpenSocial App with Google App Engine - OpenSocial http://wiki.opensocial.org/index.php?title=Building_an_OpenSocial_Ap...

Expand the admin interface to allow more granular access to the data (e.g. delete or update a single entity
rather than resetting everything at once).
Use templates (http://code.google.com/appengine/docs/gettingstarted/templates.html) to render the admin
interface.
Include timestamps in gift transactions and only show recent gifts in the app.
Allow new gifts to be added through the admin interface.
Let users send a custom message with their gift.
Create a profile view for the OpenSocial app.

Happy coding!

Resources
Developer Forums

If you have questions while working through this tutorial, you can ask other developers in one of the following
forums:

Google App Engine Developer Forum (http://groups.google.com/group/google-appengine)


OpenSocial Application Developer Forum (http://groups.google.com/group/opensocial)

Reference

Google App Engine

Getting Started Guide (http://code.google.com/appengine/docs/gettingstarted/)


Datastore Reference (http://code.google.com/appengine/docs/datastore/)
Webapp Reference (http://code.google.com/appengine/docs/webapp/)

OpenSocial

OpenSocial Tutorial (http://code.google.com/apis/opensocial/articles/tutorial/)


JavaScript API Reference (http://code.google.com/apis/opensocial/docs/reference.html)
Developer's Guide (http://code.google.com/apis/opensocial/docs/devguide.html)

Retrieved from "http://wiki.opensocial.org


/index.php?title=Building_an_OpenSocial_App_with_Google_App_Engine"

This page was last modified 04:51, 24 February 2009.

21 of 21 4/12/2009 11:56 PM

You might also like