Building An Opensocial App With Google App Engine
Building An Opensocial App With Google App Engine
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...
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):
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...
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.
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.
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.
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.
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.
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!")
# 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/).
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.
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:
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.
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:
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()
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.
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
# 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.
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:
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."
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.
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.
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
Requesting GiftTransactions
We also want to expose GiftTransactions through this api, so let's add another value to the WGSIApplication
constructor in gifts.py:
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))
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
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
application: opensocial-gifts-'''''username'''''
version: 1
runtime: python
api_version: 1
handlers:
- 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:
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.
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 './appcfg.py update <your_app_directory>;' 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...
function init() {
loadFriends();
}
function loadFriends() {
var req = opensocial.newDataRequest();
function onLoadFriends(data) {
var viewerFriends = data.get('viewerFriends').getData();
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...
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';
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('');
}
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();'''
'''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 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...
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.
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;
Notice that the callback function for this makeRequest call is loadFriends. This will basically redraw the app
after the gift transaction is processed.
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:
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()
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):
# 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:
Reference
OpenSocial
21 of 21 4/12/2009 11:56 PM