对存储在数据存储区中的图像发送“304 Not Modified”

时间:2010-05-01 17:27:14

标签: python http google-app-engine httphandler

我将用户上传的图片以db.Blob格式存储在Google App Engine数据存储区中,如the docs中所述。然后我在/images/<id>.jpg上提供这些图片。

服务器始终发送200 OK响应,这意味着浏览器必须多次下载相同的图像(==较慢)并且服务器必须多次发送相同的图像(==更昂贵)。

由于大多数图片可能永远不会改变,我希望能够发送304 Not Modified回复。我正在考虑在用户上传时计算图片的某种哈希值,然后使用它来了解用户是否已经拥有此图像(可能将哈希值发送为Etag?)

我发现this answerthis answer可以很好地解释逻辑,但我有两个问题:

  1. 是否可以在Google App Engine中发送Etag
  2. 有没有人实现过这样的逻辑,和/或是否有可用的代码片段?

4 个答案:

答案 0 :(得分:8)

Bloggart使用此技术。看看this blog post

class StaticContentHandler(webapp.RequestHandler):
  def output_content(self, content, serve=True):
    self.response.headers['Content-Type'] = content.content_type
    last_modified = content.last_modified.strftime(HTTP_DATE_FMT)
    self.response.headers['Last-Modified'] = last_modified
    self.response.headers['ETag'] = '"%s"' % (content.etag,)
    if serve:
      self.response.out.write(content.body)
    else:
      self.response.set_status(304)

  def get(self, path):
    content = get(path)
    if not content:
      self.error(404)
      return

    serve = True
    if 'If-Modified-Since' in self.request.headers:
      last_seen = datetime.datetime.strptime(
          self.request.headers['If-Modified-Since'],
          HTTP_DATE_FMT)
      if last_seen >= content.last_modified.replace(microsecond=0):
        serve = False
    if 'If-None-Match' in self.request.headers:
      etags = [x.strip('" ')
               for x in self.request.headers['If-None-Match'].split(',')]
      if content.etag in etags:
        serve = False
    self.output_content(content, serve)

答案 1 :(得分:0)

这里可能有一个更简单的解决方案。这要求您永远不会覆盖与任何标识符相关联的数据,例如修改图像会创建一个新的id(因此也是一个新的URL)。

只需将请求处理程序中的Expires标头设置为远期,例如现在+一年。这将导致客户端缓存图像,直到那时才要求更新。

这种方法有一些权衡,例如在修改图像时确保嵌入新URL,因此您必须自己决定。 jbochi提出的另一种选择是将更多逻辑放入图像请求处理程序中。

答案 2 :(得分:0)

顺便说一下,感谢webob,webapp.RequestHandler提供了检查If-None-Match的简便方法。

if etag in self.request.if_none_match:
    pass # do something

答案 3 :(得分:0)

为什么代码会使用这个:

 self.response.headers['ETag'] = '"%s"' % (content.etag,)

而不是:

 self.response.headers['ETag'] = '"%s"' % content.etag

我认为它是相同的,除非有人解释推理,否则将使用第二个。