Making Dynamically Served Images Cache Properly

16 views
Skip to first unread message

Larry....@googlemail.com

unread,
Aug 26, 2008, 4:28:21 PM8/26/08
to Google App Engine
Hi everyone!

I'm coding an AppSpot application using GAE of course. Users are
allowed to add their own avatars to their profiles and I have an
address within my site called /getavatar?id with id the user id. I can
quite easily get it to serve out the PNG images but every time I
refresh the page the images flicker slightly because they are not
cached and are being requested again by the web browser.

So my question is can I change certain information in the header to
make the web browser cache them?

At the moment my code for /getavatar looks roughly like the example in
the documentation:

class Image (webapp.RequestHandler):
def get(self):
greeting = db.get(self.request.get("img_id"))
if greeting.avatar:
self.response.headers['Content-Type'] = "image/png"
self.response.out.write(greeting.avatar)
else:
self.error(404)


I've read up on some caching tips and it seems I need to change
Expires or some other headers to certain values to make the browser
cache them. I've tried all the different ways I can find and my
results vary from nothing changing to making the images unloadable!

Any tips would be really great! Thanks,
Lster

Yaakov Sash

unread,
Aug 27, 2008, 7:08:37 AM8/27/08
to Google App Engine
You will need to add an expires or last-modified header but caching
does not work locally. Deploy your app and test it there.

On Aug 26, 4:28 pm, "Larry.Ches...@googlemail.com"

Larry....@googlemail.com

unread,
Aug 27, 2008, 8:44:07 AM8/27/08
to Google App Engine
Thanks for the reply.

I've tried adding:


self.response.headers.add_header('Expires', 'Wed, 27 Aug 2008 18:00:00
GMT')
self.response.headers.add_header('Last-Modified', 'Mon, 25 Aug 2008
18:00:00 GMT')

These two lines after setting the content type but to no avail. I am
testing it online but the images are still reloaded. Is there any
chance this is my browser's fault?

Thanks,
Larry

Barry Hunter

unread,
Aug 27, 2008, 8:54:06 AM8/27/08
to google-a...@googlegroups.com
self.response.headers['Cache-Control'] = "max-age=3600"

(or some other value in seconds)

I would also recommend changing it so the there isnt a question mark
in the url, as some (admittedly old now!) browers wont cache urls with
? in.

Ideally you would also use the last modified header and return 304's
if not changed. I dont have python code to hand but this is working
php code:

$gmdate_mod = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';

if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$if_modified_since = preg_replace('/;.*$/', '',
$_SERVER['HTTP_IF_MODIFIED_SINCE']);

if ($if_modified_since == $gmdate_mod) {
header("HTTP/1.0 304 Not Modified");
header('Content-Length: 0');
exit;
}
}

header("Last-Modified: $gmdate_mod");

get $mtime from the time the user uploaded the image (hopefully you
stored that in the datastore!) and insert this login inbetween
fetching the image (to get the date) and actully returning it.

--
Barry

- www.nearby.org.uk - www.geograph.org.uk -

Larry....@googlemail.com

unread,
Aug 27, 2008, 12:50:31 PM8/27/08
to Google App Engine
With the help of your post I have fixed the issue. Thanks lots! :)

Larry....@googlemail.com

unread,
Aug 27, 2008, 1:24:03 PM8/27/08
to Google App Engine
I spoke too soon. I made a quick test which I thought worked fully and
it does at least work partially!

class GetAvatar(webapp.RequestHandler):
def get(self):
try:
s = self.request.path.split('/')
a = s[len(s)-1]
userid = int(a)
except:
return self.error(404)

user = getuserbyid(userid)

if not user or not user.avatar:
return self.error(404)


self.response.headers['Content-Type'] = 'image/png'
self.response.headers['Last-Modified'] = 'Wed, 27 Aug 2008 18:00:00
GMT'

if self.request.headers['If-Modified-Since']:
return self.response.set_status(304)


self.response.out.write(user.avatar)


The results I get from that are that it doesn't load the image at
first but if I comment out the lines:

if self.request.headers['If-Modified-Since']:
return self.response.set_status(304)


It works fine then. After that I can put the two lines in again and it
doesn't refresh the images but does display them fine. I'm struggling
to get a Python version of your code working perfectly as I don't know
PHP at all. I think I'm very close now, perhaps I am missing
something?

Thanks for the excellent help so far,
Lster

Barry Hunter

unread,
Aug 27, 2008, 1:52:31 PM8/27/08
to google-a...@googlegroups.com
An excellent way of verifying it you have it setup:
http://www.ircache.net/cgi-bin/cacheability.py

(paste the url of a page, or just the image)

--

Larry....@googlemail.com

unread,
Aug 27, 2008, 2:55:57 PM8/27/08
to Google App Engine
I get the following output:

Expires 23 hr 6 min from now (Thu, 28 Aug 2008 18:00:00 GMT)
Cache-Control max-age=604800, public
Last-Modified 2 days 53 min ago ("Mon, 25 Aug 2008 18:00:00 GMT")
validation returned same object
ETag -
Content-Length 19.3K (19805)
Server Google Frontend


It says:

"It has a validator present, but when a conditional request was made
with it, the same object was sent anyway."

And I just need to know how to fix this. I try checking if the If-
Modified-Since header is empty or not and setting my status 304 if it
isn't empty but that doesn't work.

Thanks,
Larry
Reply all
Reply to author
Forward
0 new messages