Grayvatar 2.0

In my post about Grayvatar, I warned everybody away from actually using it for anything as it was a complete toy. The major reason I cited was that it did not make any attempt to respect cache headers. Enter Grayvatar 2.0, which is slightly less of a toy as it, at least, makes a half-hearted attempt to respect cache headers.

The key addition was httplib2, which maintains it's own cache, so that we're not hitting Gravatar's servers for every single request we receive. The other big change was to copy cache headers from the client to the server and vice-versa. This means that we'll pass a long the cache-control: max-age=300 that we get from Gravatar or the cache-control: max-age=0 we get from the client. All of this adds about 20 lines on top of the existing implementation. You still shouldn't use it for anything, it's still a toy, but at least it's slightly less naive one.

import StringIO
import tempfile
import urllib, urllib2

import cherrypy
import httplib2
import Image

from paver.easy import path

URL = "http://www.gravatar.com/avatar/%s?%s"
cache_headers = ("cache-control",)


class Grayvatar(object):
    def __init__(self):
        tmp_dir = tempfile.mkdtemp(".cache")
        self.http = http = httplib2.Http(tmp_dir)

    @cherrypy.expose
    def default(self, *bits, **kwargs):
        if bits:
            cherrypy.response.headers["Content-Type"] = "image/png"
            url = URL % (bits[0], urllib.urlencode(kwargs))
            headers, img = self.get_img(url)

            for header in cache_headers:
                if header in headers:
                    cherrypy.response.headers[header] = headers[header]

            return img_as_str(img.convert("L"))

        pwd = path(__file__).abspath().parent
        return (pwd / "how_it_works.html").text()

    def get_img(self, url):
        headers, body = self.http.request(url,
            headers = project(cherrypy.request.headers, cache_headers)
        )
        return headers, Image.open(StringIO.StringIO(body))


def project(d, keys):
    return dict((k, d[k]) for k in keys if k in d)

def img_as_str(img):
    sock = StringIO.StringIO()
    img.save(sock, "png")
    return sock.getvalue()

if __name__ == "__main__":
    cherrypy.quickstart(Grayvatar())