2010-05-01 8 views
5

Guardo las imágenes cargadas por el usuario en el almacén de datos de Google App Engine como db.Blob, como se propone en the docs. Luego sirvo esas imágenes en /images/<id>.jpg.Envíe un "304 no modificado" para las imágenes almacenadas en el almacén de datos

El servidor siempre envía una respuesta 200 OK, lo que significa que el navegador debe descargar la misma imagen varias veces (== más lento) y que el servidor debe enviar la misma imagen varias veces (== más caro).

Como la mayoría de esas imágenes probablemente nunca cambien, me gustaría poder enviar una respuesta 304 Not Modified. Estoy pensando en el cálculo de algún tipo de hash de la imagen cuando el usuario sube, y luego usar esto para saber si el usuario ya tiene esta imagen (tal vez enviar el hash como un Etag?)

he encontrado this answer y this answer que explicar la lógica bastante bien, pero tengo 2 preguntas:

  1. ¿es posible enviar un Etag en Google App Engine?
  2. ¿Alguien ha implementado tal lógica y/o hay algún fragmento de código disponible?

Respuesta

7

Bloggart utiliza esta técnica. Eche un vistazo al 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) 
+0

Excelente ejemplo! ;) –

+0

Implementé mi solución en este ejemplo, y todo funciona bien. Gracias jbochi y Nick! – Emilien

0

Puede haber una solución más simple aquí. Esto requiere que nunca sobrescriba los datos asociados con ningún identificador, p. modificar la imagen crearía una nueva identificación (y por lo tanto una nueva URL).

Simplemente configure el encabezado Expires desde su controlador de solicitudes hasta el futuro lejano, p. ahora + un año. Esto daría como resultado que los clientes guarden en la memoria caché la imagen y no pidan una actualización hasta que llegue ese momento.

Este enfoque tiene algunas ventajas y desventajas, como garantizar que las nuevas URL se incrusten cuando se modifican las imágenes, por lo que tiene que decidir por sí mismo. Lo que está proponiendo jbochi es la otra alternativa que agrega más lógica al manejador de solicitudes de imágenes.

+0

Esto de hecho sería una posible solución. Como dije, no espero que las imágenes cambien, pero nunca se sabe. Voy a analizar la solución de Nick, ya que tendría otro contenido estático que esas imágenes, y esas podrían cambiar, por lo que tener una solución para todos parece ser la mejor. ¡Gracias! – Emilien

0

Por cierto, gracias a webob, webapp.RequestHandler proporciona una manera fácil de marcar If-None-Match.

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

¿por qué el código de uso esto:

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

en lugar de esto:

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

creo que es el mismo y utilizará el segundo a menos que alguien explica el razonamiento.

Cuestiones relacionadas