Wednesday, January 25, 2012

Setting Cookie Expiration in WebApp2

In my previous post, I mentioned briefly that in IE browsers, cookie expiration need to be set by using 'expires' value, instead of 'max_age' value.

Currently, if you are using webapp2 with Google App Engine 1.6.1, you can only specify 'max_age' in the cookie, but not the 'expires' value. Because of  this, your cookies will always become session cookies in IE (expires when the browser is closed), but will have correct expiration in other browsers, eg. Firefox, Chrome, and Safari.

I posted a question in webapp2 forum about this issue, and after some back and forth conversations with Rodrigo Moraes, I got the solution for this. Here's the rundown:

set_cookie is a method defined in webob library, that is shipped with Google App Engine 1.6.1. The version that is included in GAE 1..6.1 is webob 0.9 that does not support setting 'expires' parameter in cookies. However, the later version of webob (v1.1) does support this. And what's great is that when specifying cookie with 'max_age', webob(v1.1) will set the 'expires' value too. So now your cookie will expire correctly in IE as well as in other browsers. Awesome!

The next question then, how do you configure your existing application to use the latest version of webob instead of the one that is shipped with GAE 1.6.1?

There are a couple options. If you are using Python 2.7 runtime with appengine, you can configure your app to use the latest version of webob by adding the following simple lines to your app.yaml file.

libraries:
- name: webob
  version: latest


If you are using Python 2.5 runtime instead, you will have to download webob from github, and just add it within your application directory.

Note that webob 1.1 is not backward compatible with webob 0.9, so I recommend doing a lot of testing after you updated the library.

Tuesday, January 24, 2012

My Solution to the Alphabet Soup Problem for Facebook Hacker Cup 2012

Just like last year, this year I signed up for Facebook Hacker Cup.

The qualification round just ended. Since I was busy in the weekend (it was Chinese New Year), I didn't have the chance to look into the problems until Monday, 2 hours before the round ended.

In a panic, I just picked one of the problems and tried to solve it.

I choose the Alphabet Soup for no particular reason. OK, maybe because it has a character named Alfredo Spaghetti, and because I love both Alfredo sauce and Spaghetti in general.

Here's how I solved it in Python.


file = open("alphabet_soup.txt")
number_of_lines = int(file.readline())
line = 0
print ''
while line < number_of_lines :
    line = line + 1
    test_case = file.readline()
    chars = {}
        for character in test_case:
        if character != ' ':
            if chars.has_key(character):
                chars[character] = chars[character] + 1
            else:
                chars[character] = 1
          
    h = chars['H'] if chars.has_key('H') else 0
    a = chars['A'] if chars.has_key('A') else 0
    c = chars['C'] if chars.has_key('C') else 0
    k = chars['K'] if chars.has_key('K') else 0
    e = chars['E'] if chars.has_key('E') else 0
    r = chars['R'] if chars.has_key('R') else 0
    u = chars['U'] if chars.has_key('U') else 0
    p = chars['P'] if chars.has_key('P') else 0
    i = 0
    can_continue = True
    while can_continue :
        h = h-1
        a = a-1
        c = c-2
        k = k-1
        e = e-1
        r = r-1
        u = u-1
        p = p-1
        if h >=0 and a >=0 and c >=0 and k >=0 and e >=0 and r >=0 and u >=0 and p >=0 :
            i = i + 1
        else:
            can_continue = False
    print 'Case #%s: %s' % (line, i)

For example input:
5
WELCOME TO FACEBOOK HACKERCUP
CUP WITH LABEL HACKERCUP BELONGS TO HACKER
QUICK CUTE BROWN FOX JUMPS OVER THE LAZY DOG
MOVE FAST BE BOLD
HACK THE HACKERCUP

Produces this output:
Case #1: 1
Case #2: 2
Case #3: 1
Case #4: 0
Case #5: 1
 
 

Did you participate in Facebook Hacker Cup? If so, what was your approach in solving this problem?

Friday, January 20, 2012

How to Configure Different Session Backends in Webapp2

In webapp2, sessions by default is stored in cookies.
A documentation about it can be found here:  http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html
A problem I'm encountering when storing session  information in cookies, is that there seem to be a size limit of the amount of data you can store in a cookie. What happens then, is I lost my session information, and the data that I wanted to save the session is not being saved properly.
Luckily, webapp2 allows us to save sessions in two other forms: memcache and datastore (using ndb).
This is great. So then I tried to change the session configuration to have it stored in datastore, and see if it will solve my problem with large data size.

In this post I will guide you step by step on how to configure your webapp2 application to use the datastore as the session backend.

First, let's start from the top of the documentation on how to set up sessions in webapp2 using the default cookie backend:

You will create a BaseHandler and a dispatch method that creates the session store.
import webapp2
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler):
    def dispatch(self):
        # Get a session store for this request.
        self.session_store = sessions.get_store(request=self.request)

        try:
            # Dispatch the request.
            webapp2.RequestHandler.dispatch(self)
        finally:
            # Save all sessions.
            self.session_store.save_sessions(self.response)

    @webapp2.cached_property
    def session(self):
        # Returns a session using the default cookie key.
        return self.session_store.get_session()
You will also need to create a configuration dict to be pass in to your app that defines the session secret key.
config = {}
config['webapp2_extras.sessions'] = {
    'secret_key': 'my-super-secret-key',
}

app = webapp2.WSGIApplication([
    ('/', HomeHandler),
], config=config)
 When these has been set up you can start using your sessions as follows:

# To set a value:
self.session['foo'] = 'bar'

# To get a value:
foo = self.session.get('foo')

Now, we want to use the datastore as the session backend instead of the cookie.
We'll have to modify the configuration dictionary, adding the backends dictionary :
config['webapp2_extras.sessions'] = {
    'secret_key': 'my-super-secret-key',
    'backends': {'datastore': 'webapp2_extras.appengine.sessions_ndb.DatastoreSessionFactory',
                 'memcache': 'webapp2_extras.appengine.sessions_memcache.MemcacheSessionFactory',
                 'securecookie': 'webapp2_extras.sessions.SecureCookieSessionFactory' 
}

Next, we need to modify the session provider, specifying the backend that you wanted:
@webapp2.cached_property
def session(self):
    # Returns a session using the datastore backend.
    return self.session_store.get_session(backend='datastore')

And that's all you need to do! Now whenever you save a session, you will see a new Session entity in your AppEngine datastore. 


After storing my sessions in the datatsore instead of cookie, my problem with sessions with large amount of data was resolved. So indeed there was some kind of limit when storing your session data in cookies.


I would recommend using the datastore or memcache to store your session for a few reasons.
1. If you have sensitive information to be stored, (e.g. email addresses, phone number), it will be more secure to store it in datastore than the browser's cookie.
2. Cookie is set on domain level. Depends on how your app is set up, you could encounter session issues when navigating among different domains.
3. The most important point. In webapp2, cookie expiry is set using the max-age parameter, and this parameter is not supported in IE browsers. So if you ever want to persist the session even after browser is closed, it will just not work in IE. And since IE is still one of the major browsers nowadays, you really should care about it.