Sei sulla pagina 1di 18

01.11.

2011 Building an OpenSocial App with Google App Engine - Ope


1/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Home
Invite
Profile
Get Started
Foundation
Community
Docs
Building an OpenSocial App ith Google App
Engine
From OpenSocial
Jump to: navigation, search
Lane LiaBraaten, Google Deeloper 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.
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 project on Google Code Project
Hosting. You can also install this app 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 revolucin!
Contents
[hide]
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
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
2/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
5 A simple Google App Engine web interIace
5.1 Creating a request handler
5.2 Forwarding requests
5.3 IdentiIying the user
5.4 Invoking the Admin class
6 Creating a simple data API with Google App Engine
6.1 Requesting GiIts
6.2 Requesting GiItTransactions
6.3 Recording GiItTransactions
6.4 API ReIerence
7 Serving static Iiles with Google App Engine
8 Communication between OpenSocial and Google App Engine
8.1 Publishing your app
8.2 Requesting Iriends
8.3 Requesting giIts
8.4 Requesting giIt transactions
8.5 Recording new giIt transactions
8.6 Sending and veriIying signed requests
9 Next Steps
10 Resources
10.1 Developer Forums
10.2 ReIerence
Audience
The ideal audience Ior this article knows a little about Google App Engine, and a little about OpenSocial. We'll
build this app Irom ground up, but it will help iI you've worked through the Google App Engine Getting
Started Guide and the Opensocial 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 Iamiliar 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 iI you've
got some basic web development experience, you should be Iine.
Note: You'll need a Google account and the App Engine SDK to start coding.
Architecture
By the end oI this article, you'll have constructed an OpenSocial app where user's can give each other giIts.
Along the way, we'll implement components to store, access, manipulate, and display application data (giIts
and giIt transactions). As you implement more Iunctional apps, the implementations Ior each oI these
components will likely be more complex, but the general interactions and components themselves can remain
mostly the same. This giIt-giving app has Iive such components (in order oI appearance):
1. Google App Engine app
2. Database model
3. Admin interIace
4. JSON data API
5. OpenSocial application spec
Total servers you need to maintain: 0.
Google App Engine app (app.aml and gifts.p)
Google App Engine projects are deIined and conIigured using an app.aml Iile that deIines how to handle
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
3/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Google App Engine projects are deIined and conIigured using an app.aml Iile that deIines how to handle
incoming requests. In this Iile, we'll conIigure our app to send requests to gifts.p where we'll create a
WSGIApplication that will handle these requests and send appropriate responses.
Database model (db_model.p)
The data Ior this app will be stored using the Google App Engine datastore. We'll store the giIts that are
available Ior giving as a character string Ior now. When a user gives a giIt we'll store the sender, receiver, and
giIt given as a giIt transaction. We can leverage the IDs provided by the OpenSocial API to identiIy the users
involved in a giIt transaction.
Admin interface (admin.p)
We'll use Google App Engine to host a small web application that will allow us to perIorm 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 interIace 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 where you can perIorm many useIul tasks, like
manipulating data in the datastore, viewing application logs, and accessing metrics about your app's usage.
JSON data API (api.p)
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 giIts or giIt transactions and return the data as a JSON string. The API
will also accept HTTP POST requests to add a giIt transaction to the datastore.
OpenSocial application spec (gifts.ml)
Our users will be interacting with the OpenSocial app, deIined in an application spec XML Iile. 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 applicaion idenifier Ior your application. This identiIier must be unique
across all Google App Engine applications, so Ior this article, use opensocial-giIts-sername where
sername is your username (e.g. opensocial-giIts-johndoe). II you have a Google App Engine account, log
into the Google App Engine admin console and create an app. Since App Engine is currently in a preview
release, you can only publish up to three applications Ior 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 identiIier, like
sername-dev.
The Iirst iteration oI our application will have just two Iiles: app.aml and gifts.p. The app.aml Iile
contains conIiguration details about your app, including which Python scripts to execute when your app
receives requests. Here's a simple app.aml Iile to get us started:
application: opensocial-gifts-username
version: 1
runtime: pthon
api_version: 1

handlers:
- url: /.*
script: gifts.p
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
4/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
ci: gif.
Noe: The value used Ior aicai needs to uniquely identiIy your Google app Engine app so be sure to
replace sername with your username (e.g. opensocial-giIts-jdoe).
In the a.a Iile, we speciIied that all requests should be handled by gif. using the regex /.*.
Let's deIine a WSGIAicai in gif. and create a temporary ReeHade so we can do a
quick sanity check. Here's the content in gif.:
# Sadad ibaie
import gief.hade

# AEgie i
f gge.aegie.e import eba

class SaiChec(eba.ReeHade):
def ge(ef):
ef.ee..rite("Y'e ca!")

# Ma URL ee hade class
aicai = eba.WSGIAicai([('/', SaiChec)],
debg=True)

# Fie i !
gief.hade.CGIHade().(aicai)
Now that we've got a simple app, we'll test it with the development web server. II you haven't already,
download the SDK and uncompress it. From the gge_aegie directory, run './de_aee.
<_a_diec>;'. VeriIy that you can access your app Irom a browser (the URL will be
h://cah:8080/).
Uing Google App Engine o oe daa
The original giIt-giving app uses the OpenSocial Persistence API to record the giIts that each user gives. Since
data can only be written Ior the VIEWER, the original app has to do some extra work to perIorm simple
operations. For example, to show all the giIts a person has received, the app iterates through all oI their Iriends
to see which have given them a giIt. Another potential issue with the Persistence API is that the storage limits
are set by the container. We'll circumvent both oI these issues by using Google App Engine to store our data.
Defining he daa model
We basically just need to store a history oI who gave what giIt to whom. For each giIt, we can just store the
name oI the giIt. For each giIt transaction, we need to store the sender, recipient, and a reIerence to the giIt
that was given. We can use the OpenSocial user IDs to identiIy the sender and recipient, and we can use the
db.Ke() object Ior the reIerence to the giIt object.
To deIine this model in the Google App Engine datastore, create a Iile called db_de. and insert the
Iollowing Python code:
f gge.aegie.e import db

class Gif(db.Mde):
ae = db.SigPe()

class GifTaaci(db.Mde):
ede_id = db.SigPe()
eceie_id = db.SigPe()
gif = db.RefeecePe(Gif)
This deIines two Mde classes, one to represent a giIt and one to represent a giIt transaction where a sender
gives a recipient a giIt.
Poplaing he daaoe
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
5/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Populating the datastore
Now that we have a data model, we need some way to populate it. Create a file called adi. and define an
Adi class to handle administrative operations like this. Let's start with two methods for initializing the gifts
and gift transactions in the datastore:
f db_de impo Gif, GifTaaci

cla Adi:
"""Iiiaie he i f gif i he daae."""

def iiGif(ef):
"""Deee a eiig gif ad add he defa gif."""
fo gif in Gif.a():
gif.delee()
GIFT_NAMES = ['a cahe ',
'a ea',
'a hae',
'a ed iachi ']
fo ae in GIFT_NAMES:
gif = Gif()
gif.ae = ae
gif.()

def iiGifTaaci(ef):
"""Deee a eiig gif aaci."""
fo in GifTaaci.a():
.delee()
Accessing the datastore
Now let's add a couple methods to the Adi class for accessing the gifts and gift transactions in the
datastore.
def geGifNae(ef):
ae = []
fo gif in Gif.a():
ae.aed(gif.ae)
en ae

def geGifTaaci(ef):
gifTaaci = []
fo in GifTaaci.a():
gifTaaci.aed("ede: %, eciee: %, gif: %" %
(.ede_id, .eceie_id, .gif.e()))
en gifTaaci
Great, now we can read and write data in the datastorebut how do we invoke this Python code? That's
where the admin webapp comes into play.
A simple Google App Engine eb 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
Adi class by sending a GET request to a certain URL, like http://opensocial-
gifts-sername.appspot.com/admin?action=init.
Creating a request handler
The AdiSee class will be a subclass of the ReeHade class provided by the
gge.aegie.e.eba package. We can implement a ge method that will be invoked any time
the application forwards a request to this class. Add the following import statement and class definition to
adi.:
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
6/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
adi.:
f gge.aegie.e import eba

class AdiSee(eba.ReeHade):
"""Hade ee /adi URL ad deegae he Adi ca."""

def ge(ef):
"""Hade GET ee."""
ef.ee..rite('Wece he adi eba')
Forwarding requests
First, let's get rid of the SaiChec class we started out with in gif.. Instead, import the adi
module so we can access the AdiSee class. We'll edit the WSGIAicai constructor to forward
requests for "/admin" URLs to the AdiSee class. Here's an updated version of the gif. file with
changes bolded:
# Sadad ibaie
import gief.hade

# AEgie i
f gge.aegie.e import eba

'''# OeScia Gif i
i adi'''

# Ma URL ee hade cae
aicai = eba.WSGIAicai('''[('/adi', adi.AdiSee)]''',
debg=True)

# Fie i !
gief.hade.CGIHade().(aicai)
Now hit the URL h://cah:8080/adi with our browser. You should see the admin web
application welcome message.
Identifing the user
One of the coolest features of App Engine is that it handles authentication and identit management for ou.
We onl need a few lines of code to allow for user logins and we can differentiate between regular users and
administrators with the e.ICeUeAdi method. We'll leverage this in the ge method to make
sure normal users can't access our admin console:
f gge.aegie.ai import e

class AdiSee(eba.ReeHade):
"""Hade ee /adi URL ad deegae he Adi ca."""

def ge(ef):
"""Ee ha he e i a adi."""
if e.GeCeUe():
giU = e.CeaeLgiURL(ef.ee.i)
ef.ee..rite('<a hef="%">Lgi</a>' % giU)
return

if e.ICeUeAdi():
ef.ee..rite('Y be a adi ie hi age.')
return

ef._hadeRee()

def _hadeRee(ef):
ef.ee..rite('Wece he adi eb ieface')
Browse to the admin console again and ou'll see a "Login" link. If ou login as a user that's not an
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
7/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Browse to the admin console again and ou'll see a "Login" link. If ou login as a user that's not an
administrator of the Google App Engine app, ou'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 b 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 initialies the datastore or lists the gifts and gift transactions.
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.rite(msg)
elif action == 'list':
self.response.out.rite("Gifts = %s" % admin.getGiftNames())
self.response.out.rite("<br>")
self.response.out.rite("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.rite(''.join(html))
Note that if no 'action' parameter is given (or if the value is not 'init' or 'list') the handler will displa links to
initialie the datastore or list the gift data.
Creating a simple data API with Google App Engine
Requesting Gifts
Let's start b creating a request handler that will return the list of gifts in a JSON format. If a request comes in
to http://opensocial-gifts-sername.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.rite(json.rite(gifts))
Note: The ApiServer class uses the pthon-json package. The json.py file is included in the application
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
8/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Noe: The ApiServer class uses the python-json package. The json.py Iile is included in the application
directory with the rest oI the source code. Thanks, Patrick Dlogan!
Now update the application in gifts.py to Iorward requests to the '/giIts' 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)
Reeing GifTanacion
We also want to expose GiItTransactions 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 oI GET requests coming into the ApiServer and we can diIIerentiate 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.rite(json.rite(gifts))

def _handleGiftTransactions(self):
#TODO(you) return a list of GiftTransactions as JSON
In the last snippet, the code Ior returning the list oI giIts was moved into the _handleGifts section. Now we
need to implement the _handleGiftTransactions method.
We should expect requests Ior two lists oI GiftTransactions: a list oI giIts a user has sent and a list oI
giIts a user has received. Let's design the API to accept the sender or receiver ID as a URL parameter and
determine the list oI GiftTransactions to return based on the values oI these parameters.
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.rite(json.rite(results))

01.11.2011 Building an OpenSocial App with Google App Engine - Ope
9/18 wiki.opensocial.myspace.com/index.php?title=Building_an_

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)
ele:
results = GiftTransaction.all()

en 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_ke')).ke()
giftTransaction.put()
API Reference
Here's a summary of the API we just built:
HTTP
Method
URL Description Eample Response
GET /gifts
Returns the names and keys of all gifts in the datastore
as a JSON array.
[{"name" : "a peanut",
"key" : "ABC",
{"name" : "a
cashew",
"key" : "XYZ"]
GET
/giftTransactions?
receiver_id=xxxx
Returns an array of gift transactions where the receiver
is specified by the URL parameter receiver_id.
[{"sender_id":"yyyy",
"receiver_id":"xxxx",
"gift_key":"XYZ",
{"sender_id":"zzzz",
"receiver_id":"xxxx",
"gift_key":"ABC"]
GET
/giftTransactions?
sender_id=xxxx
Returns an array of gift transactions where the sender
is specified by the URL parameter sender_id.
[{"sender_id":"xxxx",
receiver_id:"yyyy",
gift_key:"XYZ",
{"sender_id":"xxxx",
receiver_id:"zzzz",
gift_key:"XYZ"]
HTTP
Method
URL Description Eample POST data
POST /giftTransactions
Creates a new gift transaction
in the datastore based on the
sender, receiver, and gift key
specified in the POST data.
sender_id=xxxx&receiver_id=yyyy&gift_key=XYZ
Sering static files ith Google App Engine
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
10/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Serving static files with Google App Engine
You can conIigure your app to serve up static content by editing the app.aml Iile. Just speciIy the URL oI
the static directory and the name oI the directory that will contain static content. Here's a copy oI the
app.aml Iile with these changes bolded:
application: opensocial-gifts-'''''username'''''
version: 1
runtime: pthon
api_version: 1

handlers:

'''- url: /static
static_dir: static'''

- url: /.*
script: gifts.p
Note: Google App Engine caches static Iiles Ior 10 minutes by deIault, but you can control this caching
period in the app.aml Iile. You'll Iind the details in the ConIiguring an App documentation.
In your application directory, create a directory called static. This directory is where we'll keep our
OpenSocial app spec. Create a Iile called gifts.ml in the static directory and add the Iollowing content:
<?ml version="1.0" encoding="UTF-8"?>;
<Module>;
<ModulePrefs title="Gifts" />;
<Content tpe="html">;
<![CDATA[
Hello, Google App Engine!
]]>;
</Content>;
</Module>;
Browse to http://localhost:8080/static/gifts.ml and veriIy that you can see your OpenSocial
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 oI giIts and
Iriends and displaying them in drop-down menus. Then we'll request the giIt transactions Irom the data API
we built. Finally, we'll let the user actually give a giIt by POSTing a new giIt transaction to the data API.
Publishing our app
Up to this point, we've been using the (local) development app server, but in order Ior an OpenSocial
container like orkut or MySpace to access your application spec, the Iile needs to be hosted publicly. From
the google_appengine directory, run &apos;./appcfg.p update <our_app_director>;&apos;
Irom the application directory to publish your app. AIter the upload is complete, make sure you can access
the OpenSocial application spec XML Iile at http://opensocial-giIts-sername/static/giIts.xml Irom your
browser.
Requesting friends
The Iollowing application will request the list oI Iriends Irom the OpenSocial container when the page loads
and display the Iriends in a drop-down menu.
<?ml version="1.0" encoding="UTF-8"?>;
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
11/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
<?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 speciIies the
onLoadFriends method as a callback Iunction. The onLoadFriends method receives a DataResponse
object that contains an opensocial.Collection oI opensocial.Person objects which represent the
user's Iriends. The name oI each oI these Iriends is then added as an option in the drop-down menu. The
drop-down menu, a <select>; element, is then inserted into the 'Iriends' span.
Reqeing gif
Next, let's add a drop-down menu Ior the list oI giIts. We'll request the giIt data, Iormat it into a drop-down
menu, and add it to the HTML in a 'giIts' span. First, add the 'giIts' span at the end oI the application spec:
<?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.
fncion init() {
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
12/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
fncion init() {
loadFriends();
'''loadGifts();'''


'''function loadGifts() {
var params = {;
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
var url = 'http://openocial-gif-'''''ename'''''.apppo.com/gif';

gadgets.io.makeRequest(url, onLoadGifts, params);
'''
The loadGifts method uses the gadgets.io.MakeRequest method to send a request to the JSON data
API Ior the array oI available giIts. Once we get the response, the callback method, onLoadGifts, will
display the giIts in a drop-down menu.
fncion onLoadGifts(response) {
ar gifts = response.data;
ar html = ne Array();
html.push('<select id="nut">;');
for (ar 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('');

Reeing gif anacion


Next we want to display the giIts 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 oI our Iriends (so the app can display names instead
oI user IDs). We can build this object at the same time that we build the drop-down menu oI the user's Iriends,
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, modiIy the onLoadFriends method so it
looks like this:
fncion onLoadFriends(data) {
ar viewer = data.get('viewer').getData();
ar viewerFriends = data.get('viewerFriends').getData();
'''var friends = new Array();'''

html = ne Array();
html.push('<select id="person">;');
viewerFriends.each(fncion(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 Ietch this data Irom the
data API and use makeRequest to send the requests Ior giIt transaction data. Each call to makeRequest
speciIies 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 oI the data request. Here is the implementation Ior the
loadGiftTransactions method and the two callbacks:
fncion loadGiftTransactions(viewer, friends) {
// Ge he gif anacion hee he VIEWER i he ende
ar url = 'http://opensocial-gifts-'''''username'''''/giftTransactions?sender_id=' + viewer.getId();
gadgets.io.makeRequest(url, onLoadGiftsGivenClosure(friends));

// Ge he gif anacion hee he VIEWER i he eceie
ar url = 'http://opensocial-gifts-'''''username'''''/giftTransactions?receiver_id=' + viewer.getId();
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
13/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
ar url = 'http://opensocial-gifts-'''''username'''''/giftTransactions?receiver_id=' + viewer.getId();
gadgets.io.makeRequest(url, onLoadGiftsReceivedClosure(friends));


fncion onLoadGiftsGivenClosure(friends) {
rern fncion(response) {
ar giftTransactions = gadgets.json.parse(response.data);
ar html = ne Array();
html.push('You have given:');
html.push('<ul>');
for (ar 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();


fncion onLoadGiftsReceivedClosure(friends) {
rern fncion(response) {
ar giftTransactions = gadgets.json.parse(response.data);
ar html = ne Array();
html.push('You have received:<ul>');
for (ar 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 displaing these gift transactions ma take up more height than the container provides b default, these
callbacks use gadgets.window.adjustHeight to resie the app after inserting the data into the page. In
order to use this new method, ou need to include <Require feature="dynamic-height"/> in the
<ModulePrefs> element of the application spec.
Finall, add the 'given' and 'received' <div> elements to the HTML section of the application spec:
<?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 ne gift transactions
The last piece for functionalit to add is the abilit for a user to actuall 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>
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
14/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
<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 Iunctions, always use href="javascript:void(0);". Using
href="#" causes unexpected results in the container.
The giftGive Iunction just requests the VIEWER Irom the container and sends the receiver and giIt key data
to the callback Iunction Ior 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 oI the giIt given as post data.
fncion giveGift() {
ar gift_key = document.getElementById('nut').value;
ar receiver_id = document.getElementById('person').value;

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


fncion postGiftTransactionClosure(receiver_id, gift_key) {
rern fncion(response) {
ar sender_id = response.get('viewer').getData().getId();
ar 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;
ar url = http://openocial-gif-'''''ename'''''/gifTanacion';

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

Notice that the callback Iunction Ior this makeRequest call is loadFriends. This will basically redraw the
app aIter the giIt transaction is processed.
Sending and verifing 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 Irom your OpenSocial app, just change the parameters sent to makeRequest as
Iollows:
fncion postGiftTransactionClosure(receiver_id, gift_key) {
rern fncion(response) {
ar sender_id = response.get('viewer').getData().getId();
ar params = {;
'''params[gadgets.io.RequestParameters.AUTHORIZATION = gadgets.io.AuthorizationType.SIGNED;'''
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
15/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
'''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;
ar 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 accessif a request is
forged, you can reply with an error message or nothing at all.
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 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, which has been parsed, converted to
hex value, and hard-coded in the public_key_str variable.
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\
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
16/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
4983c7be0be325ce8dfc3af6860f7a\
b0bf32742cd9fb2fcd1cd1756bbc40\
0b743f73acefb45d26694caf4f26b9\
765b9f65665245524de957e8c547c3\
58781fdfb68ec056d1"""
public_ke_long = long(public_ke_str, 16)
public_ke = RSA.construct((public_ke_long, eponent))

# Rebuild the message hash locall
oauth_request = oauth.OAuthRequest(http_method=self.request.method,
http_url=self.request.url,
parameters=self.request.params.mied())
message = '&'.join((oauth.escape(oauth_request.get_normalied_http_method()),
oauth.escape(oauth_request.get_normalied_http_url()),
oauth.escape(oauth_request.get_normalied_parameters()),))
local_hash = hashlib.sha1(message).digest()

# Appl the pblic ke to the signature from the remote host
sig = urllib.unquote(self.request.params.mied()["oauth_signature"]).decode('base64')
remote_hash = public_ke.encrpt(sig, '')[0][-20:]

# Verif that the locall-built value matches the value from the remote server.
rern local_hash==remote_hash
The _isValidSignature method uses two third party modules. OAuth's Python client library was written
by Leah Culver and the RSA code is just a small piece oI the pycrypto toolkit. Score one Ior open source!
In case you didn't cut and paste everything just right, this application is hosted in the opensocial-giIts Google
Code project where you can Iind the OpenSocial application spec, the Iull implementation oI the JSON data
API, and all the other Iiles used in this application.
Net Steps
This tutorial has only scratched the surIace oI what you can do with Google App Engine and OpenSocial. Try
adding some oI these Ieatures to your app:
Display pictures Ior each giIt instead oI text (Hint: store the images in your static directory).
Expand the admin interIace to allow more granular access to the data (e.g. delete or update a single
entity rather than resetting everything at once).
Use templates to render the admin interIace.
Include timestamps in giIt transactions and only show recent giIts in the app.
Allow new giIts to be added through the admin interIace.
Let users send a custom message with their giIt.
Create a proIile view Ior the OpenSocial app.
Happy coding!
Resources
Developer Forums
II you have questions while working through this tutorial, you can ask other developers in one oI the Iollowing
Iorums:
Google App Engine Developer Forum
OpenSocial Application Developer Forum
Reference
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
17/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
Reference
Google App Engine
Geing Saed Gide
Daaoe Refeence
Webapp Refeence
OpenSocial
OpenSocial Toial
JaaScip API Refeence
Deelope' Gide
Reieed fom "hp://iki.openocial.mpace.com/inde.php?
ile=Bilding_an_OpenSocial_App_ih_Google_App_Engine"
Vies
Page
Dicion
Vie oce
Hio
Personal tools
Log in / ceae accon
Naigation
Main Page
Conaine
JS API Refeence
Aicle & Toial
Conibing
Recen change
Random page
Help
Search
Go Seach
Toolbo
Wha link hee
Relaed change
Special page
Pinable eion
Pemanen link
Thi page a la modified 23:57, 26 Noembe 2010.
01.11.2011 Building an OpenSocial App with Google App Engine - Ope
18/18 wiki.opensocial.myspace.com/index.php?title=Building_an_
This page was last modified 23:57, 26 November 2010.
This page has been accessed 42,903 times.
Privac polic
About OpenSocial
Disclaimers

Potrebbero piacerti anche