My favorites | English | Sign in

Google Friend Connect APIs (Labs)

The Chow Down - Server-side integration walkthrough

Screenshot showing The Chow Down's logged in view

The Chow Down (TCD) is a sample website which implements a user registration and login system. The site is integrated with Google Friend Connect and allows Friend Connect users to access the site without registering. Additionally, these users will be able to see information about their friends that have joined the site, and are able to interact with these friends through the site's UI.

This article covers the design of The Chow Down and should serve as a guide to show how such an integration can be accomplished. You will see how multiple APIs work together to provide the site's functionality, and how Google Friend Connect data can be accessed from your server, without the need for JavaScript data requests.

The source code of The Chow Down has been open sourced under an Apache 2 license. You may browse the source online or download a copy for reference.

Contents

  1. Overview
  2. Handling the request flow - controller.py
  3. Representation of data - models.py
  4. Providers
    1. Choosing the right session - sessions.py
    2. Getting user data - users.py
    3. Getting restaurant data - restaurants.py
  5. Packaging it together - views.py

Overview

This sample is a demonstration of how Google Friend Connect can be tightly integrated into a website with an existing login mechanism. Note that the functionality for users who are not Friend Connect members is preserved - the intent is to show that adding Friend Connect extends an existing site and delivers a richer social experience to users who elect to take advantage of the new functionality.

The Chow Down site extends the standard Google App Engine webapp framework to follow a simplified model, view, controller design. Most of the work involved in processing a request takes place in controller.py and the various data providers. Units of data are passed around the application as models which can be cached in memory or saved to the data store. Collections of user and restaurant data models are requested by the various views and injected into templates, which are rendered to the viewer.

Handling the request flow - controller.py

The lifecycle of each request is mostly determined by the code in controller.py. Here, a class named RequestHandler inherits from the App Engine webapp.RequestHandler and processes the request object. When the initialize method is called by App Engine with a request, RequestHandler does the following:

  1. Creates a session provider instance and adds it to the request.
  2. Creates a user provider instance and adds it to the request.

The rest of the controller methods provide convenience functionality, such as setting cookies, handling exceptions, reporting messages to the user, and requiring certain parameters from a request. There are also plug-in classes to change the rendering output of a given response, from a template to a pure JSON representation, which can be useful for Ajax heavy apps.

Representation of data - models.py

The models used in The Chow Down are mostly simple App Engine datastore models, with one exception - the User class inherits from PolyModel (which grants it the ability to be subclassed) and in turn, is subclassed by the FriendConnectUser class, which adds no persistent data fields, but does add a private property named __person and several accessor methods for this property.

The rationale for the design of FriendConnectUser is that the app should not store any profile data from a Friend Connect user, save the ID number needed to look that person up again, as Friend Connect user data is fairy volatile and likely to change. So FriendConnectUser instances keep track of an ID number and need to be populated either by a request to a Friend Connect server, or a cached Person result before being used by your application. Simply pulling a FriendConnectUser from the data store would not even return a human-readable display name to use. Thankfully, the user provider system used by the site allows for fetching this data when needed.

Providers

Data sources in TCD are called "providers". Each provider groups together related functionality, such as session data storage, fetching user profiles, or requesting data about restaurants from a web service.

Choosing the right session - sessions.py

The session store keeps track of user-scoped data across multiple requests to the site. Data about the current page viewer is cached in the session so that the server does not have to make a http fetch from the Friend Connect servers upon every request. This speeds up the app significantly, but means that every user needs a unique identifier that will keep their data separate from other users - one user seeing another user's session data can be a large security vulnerability!

Sessions in The Chow Down are tracked using cookies. Since Friend Connect automatically creates a cookie containing a security token when a user logs in, TCD uses this security token as a session identifier if it exists, defaulting to a randomly generated session ID if no Friend Connect cookie is found.

When a session ID is found, the session is loaded and checked to see whether a viewer has been stored in the session. If the viewer is there, the user is considered logged in. Otherwise, the session exists, but the user may not be authenticated or their security token may be expired.

A complicated case arises when a user is logged in with both Google Friend Connect and a local account. The Chow Down merges both accounts in this case. Here's an overview of the algorithm which RequestHandler's get_session method implements:

  No cookies Local cookie GFC cookie Local and GFC cookies
No viewers New session started Local session ID used Local session ID used Local session ID used
Local viewer N/A Local user logged in N/A Local user logged in
GFC viewer N/A N/A GFC user logged in GFC user logged in, local session ended
Local and GFC viewers N/A N/A N/A Local user converted to GFC user, local session ended

Getting user data - users.py

When dealing with users from multiple sources, you must take into account the potential need for a user from one source to request data from a completely different source. Requests for local user data are easy, since the application has full control over its own data store. Requests for arbitrary Friend Connect user data must be done using 2-legged OAuth requests. Requests for the current user's data (for example, the current user's friends list) should be done using the fcauth parameter.

The Chow Down uses a system of user provider objects which inherit from each other to make requests for user data. The general chain of inheritance is FCAuthProvider -> TwoLeggedProvider -> LocalProvider, and which type of object is attached to each request depends on the type of viewer:

  Local User GFC User
Provider TwoLeggedProvider FCAuthProvider

Note that a LocalProvider is never created directly, but its methods are called by the classes which inherit from it, as shown in the following table:

  Local user GFC user Friends
Local user requests... TwoLeggedProvider -> LocalProvider TwoLeggedProvider None
GFC user requests... FCAuthProvider -> LocalProvider FCAuthProvider -> TwoLeggedProvider FCAuthProvider

Restaurants - restaurants.py

The Chow Down uses the Yahoo Query Language API along with the Google Maps API to provide data about restaurants for the functionality of the apps. Most of the functionality to query for and bookmark restaurant data is located in restaurants.py.

Packaging it together - views.py

The code in views.py maps urls to controller.RequestHandler subclasses. Each subclass is responsible for collecting the appropriate data for the view and passing it to the request handler's render function. By taking advantage of Python's ability to inherit from multiple classes, TCD splits views up into atomic sets of data, and combines multiple sets for the data-heavy compound views.

For an example, examine the structure of two atomic data classes, JsonRestaurantsView and JsonFriendsView:

class JsonRestaurantsView(controller.JsonRenderer):    
  def get_data(self):
    data = super(JsonRestaurantsView, self).get_data()
    # Adds data about the restaurants here
    return data

  def get(self):
    self.render()
      
class JsonFriendsView(controller.JsonRenderer):    
  def get_data(self):
    data = super(JsonFriendsView, self).get_data()
    # Adds data about the restaurants here
    return data

  def get(self):
    self.render()

Because both of these classes inherit from the controller.JsonRenderer class, we can map them to a url and they would each return a JSON-encoded representation of some restaurants and friends, respectively:

if __name__ == '__main__':
  handlers = [ 
      ('/json/friends', JsonFriendsView),
      ('/json/restaurants', JsonRestaurantsView),
  ]
  run_wsgi_app(webapp.WSGIApplication(handlers, debug=settings.DEBUG))

JSON-encoded data is great for certain types of requests, but sometimes it is useful to return a small portion of data in a rendered format and use it to update an element on another page via an Ajax request. For the purpose of outputting rendered HTML content, the site controller file contains a TemplateRenderer class, which maps the data from the class to a template file in the templates project directory:

class AjaxFriendsView(controller.TemplateRenderer, JsonFriendsView): 
  def get_template(self):
    return "ajax_friends.html"

class AjaxRestaurantsView(controller.TemplateRenderer, JsonRestaurantsView):
  def get_template(self):
    return "ajax_restaurants.html"

The two classes above now return content suitable for dynamically updating another page via an Ajax request (assuming you bind these classes to urls!). Now to create a single page with both restaurants and friends displayed on it, you just need a class that inherits from both AjaxFriendsView and AjaxRestaurantsView:

class IndexView(AjaxFriendsView, AjaxRestaurantsView):   
  def get_template(self):
    return "index.html"

When mapped to a url and rendered, IndexView will render the index.html template with the data from both AjaxRestaurantsView and AjaxFriendsView. Simple!