Información general sobre la API Blobstore para servicios agrupados antiguos

La API Blobstore permite que tu aplicación sirva objetos de datos, llamados blobs,que son mucho más grandes que el tamaño permitido para los objetos del servicio Datastore. Los blobs resultan útiles para mostrar archivos de gran tamaño, como archivos de vídeo o de imagen, y para permitir a los usuarios subir archivos de datos grandes. Los blobs se crean al subir un archivo mediante una solicitud HTTP. Normalmente, tus aplicaciones lo harán mostrando un formulario con un campo de subida de archivos al usuario. Cuando se envía el formulario, Blobstore crea un blob a partir del contenido del archivo y devuelve una referencia opaca al blob, llamada clave de blob,que puedes usar más adelante para servir el blob. La aplicación puede proporcionar el valor completo del blob en respuesta a una solicitud de un usuario o leer el valor directamente mediante una interfaz de tipo archivo de streaming.

Introducción al almacén de blob

Google App Engine incluye el servicio Blobstore, que permite a las aplicaciones servir objetos de datos limitados únicamente por la cantidad de datos que se pueden subir o descargar a través de una sola conexión HTTP. Estos objetos se denominan valores de Blobstore o blobs. Los valores del almacén de blob se muestran como respuestas de los controladores de solicitudes y se crean como archivos subidos a través de formularios web. Las aplicaciones no crean datos de blob directamente, sino de forma indirecta, mediante un formulario web enviado u otra solicitud HTTP POST. Los valores de Blobstore se pueden proporcionar al usuario o la aplicación puede acceder a ellos en un flujo similar a un archivo mediante la API Blobstore.

Para pedir a un usuario que suba un valor de Blobstore, tu aplicación muestra un formulario web con un campo de subida de archivos. La aplicación genera la URL de acción del formulario llamando a la API Blobstore. El navegador del usuario sube el archivo directamente a Blobstore a través de la URL generada. A continuación, Blobstore almacena el blob, reescribe la solicitud para que contenga la clave del blob y la envía a una ruta de tu aplicación. Un controlador de solicitudes en esa ruta de tu aplicación puede realizar un procesamiento adicional del formulario.

Para servir un blob, tu aplicación define un encabezado en la respuesta saliente y App Engine sustituye la respuesta por el valor del blob.

Los blobs no se pueden modificar una vez creados, pero sí se pueden eliminar. Cada blob tiene un registro de información de blob correspondiente, almacenado en Datastore, que proporciona detalles sobre el blob, como la hora de creación y el tipo de contenido. Puedes utilizar la clave de blob para extraer registros de información del blob y realizar una consulta sobre sus propiedades.

Una aplicación puede leer un valor de Blobstore por partes mediante una llamada a la API. Cada parte puede tener el tamaño máximo permitido de un valor devuelto por el API. Este tamaño es un poco inferior a 32 megabytes, que se representa en Python con la constante google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE. Una aplicación no puede crear ni modificar valores de Blobstore, excepto a través de archivos que haya subido el usuario.

Uso del almacén de blob

Las aplicaciones pueden usar Blobstore para aceptar archivos grandes que los usuarios suban y para ofrecer esos archivos. Los archivos se denominan blobs una vez que se han subido. Las aplicaciones no acceden a los blobs directamente. En su lugar, las aplicaciones trabajan con blobs a través de entidades de información de blob (representadas por la clase BlobInfo) en el almacén de datos.

El usuario crea un blob cuando envía un formulario HTML que incluye uno o varios campos de entrada de archivos. Tu aplicación llama a create_upload_url() para obtener el destino (acción) de este formulario y le pasa a la función una ruta de URL de un controlador de tu aplicación. Cuando el usuario envía el formulario, el navegador del usuario sube los archivos especificados directamente a Blobstore. Blobstore reescribe la solicitud del usuario y almacena los datos del archivo subido, sustituyéndolos por una o varias claves de blob correspondientes. A continuación, envía la solicitud reescrita al controlador en la ruta de URL que has proporcionado a create_upload_url(). Este controlador puede llevar a cabo procesos adicionales de acuerdo con la clave de blob.

La aplicación puede leer partes de un valor de Blobstore mediante una interfaz de streaming similar a un archivo. Consulta La clase BlobReader.

Subir un blob

Para crear y subir un blob, sigue este procedimiento:

1. Crear una URL de subida

Llama a blobstore.create_upload_url() para crear una URL de subida del formulario que rellenará el usuario. Pasa la ruta de la aplicación que se cargará cuando se complete el POST del formulario.

upload_url = blobstore.create_upload_url("/upload_photo")

También hay una versión asíncrona, create_upload_url_async(). Permite que el código de tu aplicación siga ejecutándose mientras Blobstore genera la URL de subida.

2. Crear un formulario de subida

El formulario debe incluir un campo de subida de archivos y el valor de enctype del formulario debe ser multipart/form-data. Cuando el usuario envía el formulario, la API Blobstore gestiona el POST, que crea el blob. La API también crea un registro de información del blob, lo almacena en el almacén de datos y envía la solicitud reescrita a tu aplicación en la ruta indicada como clave de blob.

        # To upload files to the blobstore, the request method must be "POST"
        # and enctype must be set to "multipart/form-data".
        self.response.out.write(
            """
<html><body>
<form action="{0}" method="POST" enctype="multipart/form-data">
  Upload File: <input type="file" name="file"><br>
  <input type="submit" name="submit" value="Submit">
</form>
</body></html>""".format(
                upload_url
            )
        )

Debes servir la página del formulario con un Content-Type de text/html; charset=utf-8. De lo contrario, los nombres de archivo con caracteres no ASCII se interpretarán de forma incorrecta.
Este es el tipo de contenido predeterminado en webapp y webapp2, pero debes recordarlo si defines tu propio Content-Type o no utilizas webapp.

No puedes usar un balanceador de carga de aplicación externo global con un NEG sin servidor para gestionar las solicitudes de subida enviadas a la URL /_ah/upload/ devuelta por la llamada blobstore.create_upload_url. En su lugar, debe dirigir esas solicitudes de subida directamente al servicio de App Engine. Para ello, puedes usar el dominio appspot.com o un dominio personalizado asignado directamente al servicio de App Engine.

3. Implementar un controlador de subida

En este controlador, puede almacenar la clave del blob con el resto del modelo de datos de su aplicación. Se puede seguir accediendo a la clave de blob desde la entidad de información del blob en el almacén de datos. Ten en cuenta que, una vez que el usuario envía el formulario y se invoca tu controlador, el blob ya está guardado y su información se ha añadido al almacén de datos. Si tu aplicación no quiere conservar el blob, debes eliminarlo inmediatamente para evitar que se quede huérfano:

class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload = self.get_uploads()[0]
        user_photo = UserPhoto(
            user=users.get_current_user().user_id(), blob_key=upload.key()
        )
        user_photo.put()

        self.redirect("/view_photo/%s" % upload.key())

El framework de aplicaciones web proporciona la clase de controlador de subida blobstore_handlers.BlobstoreUploadHandler para ayudarte a analizar los datos del formulario. Para obtener más información, consulta la referencia de BlobstoreUploadHandler.

Cuando Blobstore reescribe la solicitud del usuario, se vacían los cuerpos de las partes MIME de los archivos subidos y se añade la clave de blob como encabezado de parte MIME. Se conservarán todos los demás campos y partes del formulario y se transmitirán al controlador de subida. Si no especifica un tipo de contenido, Blobstore intentará deducirlo a partir de la extensión del archivo. Si no se puede determinar el tipo de contenido, se asigna el tipo de contenido application/octet-stream al blob recién creado.

Servir un blob

Para servir blobs, debes incluir un controlador de descarga de blobs como ruta en tu aplicación. La aplicación sirve un blob configurando un encabezado en la respuesta saliente. En el siguiente ejemplo se usa el framework webapp. Cuando se usa webapp, el controlador debe pasar la clave del blob deseado a self.send_blob(). En este ejemplo, la clave del blob se transmite al controlador de descarga como parte de la URL. En la práctica, el controlador de descarga puede obtener la clave de blob mediante cualquier método que selecciones, ya sea cualquier otro método o una acción de usuario.

class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, photo_key):
        if not blobstore.get(photo_key):
            self.error(404)
        else:
            self.send_blob(photo_key)

El framework de aplicaciones web proporciona la clase de controlador de descargas blobstore_handlers.BlobstoreDownloadHandler para ayudarte a analizar los datos del formulario. Para obtener más información, consulta la referencia de BlobstoreDownloadHandler.

Los blobs se pueden servir desde cualquier URL de aplicación. Para servir un blob en tu aplicación, debes incluir un encabezado especial en la respuesta que contenga la clave del blob. App Engine sustituye el cuerpo de la respuesta por el contenido del blob.

Intervalos de bytes de blobs

El almacén de blob admite la publicación de parte de un valor grande en lugar del valor completo en respuesta a una solicitud. Para servir un valor parcial, incluye el encabezado X-AppEngine-BlobRange en la respuesta saliente. Su valor es un intervalo de bytes HTTP estándar. La numeración de bytes se basa en cero. Un X-AppEngine-BlobRange en blanco indica a la API que ignore el encabezado de intervalo y que proporcione el blob completo. Entre los ejemplos de intervalos se incluye:

  • 0-499 sirve los primeros 500 bytes del valor (bytes del 0 al 499, ambos incluidos).
  • 500-999 sirve 500 bytes a partir del byte 501.
  • 500- sirve todos los bytes a partir del byte 501 hasta el final del valor.
  • -500 sirve los últimos 500 bytes del valor.

Si el intervalo de bytes es válido para el valor de Blobstore, Blobstore envía el código de estado 206 Partial Content y el intervalo de bytes solicitado al cliente. Si el intervalo no es válido para el valor, Blobstore envía 416 Requested Range Not Satisfiable.

Blobstore no admite varios intervalos de bytes en una sola solicitud (por ejemplo, 100-199,200-299), tanto si se solapan como si no.

La clase webapp.blobstore_handlers.BlobstoreDownloadHandler incluye funciones para definir este encabezado mediante los índices de bytes proporcionados y para derivar el intervalo de bytes automáticamente a partir de un encabezado range proporcionado por el usuario.

Aplicación de ejemplo completa

En la siguiente aplicación de ejemplo, la URL principal de la aplicación carga el formulario que pide al usuario que suba un archivo, y el controlador de subida llama inmediatamente al controlador de descarga para servir los datos. Esto se hace para simplificar la aplicación de ejemplo. En la práctica, probablemente no utilizarías la URL principal para solicitar los datos de subida ni tampoco publicarías de forma inmediata un blob que acabaras de subir.

from google.appengine.api import users
from google.appengine.ext import blobstore
from google.appengine.ext import ndb
from google.appengine.ext.webapp import blobstore_handlers
import webapp2


# This datastore model keeps track of which users uploaded which photos.
class UserPhoto(ndb.Model):
    user = ndb.StringProperty()
    blob_key = ndb.BlobKeyProperty()


class PhotoUploadFormHandler(webapp2.RequestHandler):
    def get(self):
        upload_url = blobstore.create_upload_url("/upload_photo")
        # To upload files to the blobstore, the request method must be "POST"
        # and enctype must be set to "multipart/form-data".
        self.response.out.write(
            """
<html><body>
<form action="{0}" method="POST" enctype="multipart/form-data">
  Upload File: <input type="file" name="file"><br>
  <input type="submit" name="submit" value="Submit">
</form>
</body></html>""".format(
                upload_url
            )
        )


class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload = self.get_uploads()[0]
        user_photo = UserPhoto(
            user=users.get_current_user().user_id(), blob_key=upload.key()
        )
        user_photo.put()

        self.redirect("/view_photo/%s" % upload.key())




class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, photo_key):
        if not blobstore.get(photo_key):
            self.error(404)
        else:
            self.send_blob(photo_key)




app = webapp2.WSGIApplication(
    [
        ("/", PhotoUploadFormHandler),
        ("/upload_photo", PhotoUploadHandler),
        ("/view_photo/([^/]+)?", ViewPhotoHandler),
    ],
    debug=True,
)

Usar el servicio Imágenes con Blobstore

El servicio de imágenes puede utilizar un valor del almacén de blob como fuente de una transformación. La imagen original puede tener el tamaño máximo permitido para un valor del almacén de blob. El servicio Imágenes sigue devolviendo la imagen transformada a la aplicación, por lo que la imagen transformada debe tener un tamaño inferior a 32 megabytes. Esto resulta útil para crear imágenes en miniatura de fotografías grandes que suben los usuarios.

Para obtener información sobre cómo usar el servicio de imágenes con valores de Blobstore, consulta la documentación del servicio de imágenes.

Usar la API Blobstore con Google Cloud Storage

Puedes usar la API Blobstore para almacenar blobs en Cloud Storage en lugar de en Blobstore. Debes configurar un segmento tal como se describe en la documentación de Google Cloud Storage y especificar el segmento y el nombre de archivo en el parámetro gs_bucket_name de blobstore.blobstore.create_upload_url. En el controlador de subida, debes procesar los metadatos de FileInfo devueltos y almacenar explícitamente el nombre de archivo de Google Cloud Storage necesario para recuperar el blob más adelante.

También puedes servir objetos de Cloud Storage mediante la API Blobstore. En los siguientes fragmentos de código se muestra cómo hacerlo:

# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A sample app that operates on GCS files with blobstore API."""

import cloudstorage
from google.appengine.api import app_identity
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers
import webapp2


# This handler creates a file in Cloud Storage using the cloudstorage
# client library and then reads the data back using the Blobstore API.
class CreateAndReadFileHandler(webapp2.RequestHandler):
    def get(self):
        # Get the default Cloud Storage Bucket name and create a file name for
        # the object in Cloud Storage.
        bucket = app_identity.get_default_gcs_bucket_name()

        # Cloud Storage file names are in the format /bucket/object.
        filename = "/{}/blobstore_demo".format(bucket)

        # Create a file in Google Cloud Storage and write something to it.
        with cloudstorage.open(filename, "w") as filehandle:
            filehandle.write("abcde\n")

        # In order to read the contents of the file using the Blobstore API,
        # you must create a blob_key from the Cloud Storage file name.
        # Blobstore expects the filename to be in the format of:
        # /gs/bucket/object
        blobstore_filename = "/gs{}".format(filename)
        blob_key = blobstore.create_gs_key(blobstore_filename)

        # Read the file's contents using the Blobstore API.
        # The last two parameters specify the start and end index of bytes we
        # want to read.
        data = blobstore.fetch_data(blob_key, 0, 6)

        # Write the contents to the response.
        self.response.headers["Content-Type"] = "text/plain"
        self.response.write(data)

        # Delete the file from Google Cloud Storage using the blob_key.
        blobstore.delete(blob_key)


# This handler creates a file in Cloud Storage using the cloudstorage
# client library and then serves the file back using the Blobstore API.
class CreateAndServeFileHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self):
        # Get the default Cloud Storage Bucket name and create a file name for
        # the object in Cloud Storage.
        bucket = app_identity.get_default_gcs_bucket_name()

        # Cloud Storage file names are in the format /bucket/object.
        filename = "/{}/blobstore_serving_demo".format(bucket)

        # Create a file in Google Cloud Storage and write something to it.
        with cloudstorage.open(filename, "w") as filehandle:
            filehandle.write("abcde\n")

        # In order to read the contents of the file using the Blobstore API,
        # you must create a blob_key from the Cloud Storage file name.
        # Blobstore expects the filename to be in the format of:
        # /gs/bucket/object
        blobstore_filename = "/gs{}".format(filename)
        blob_key = blobstore.create_gs_key(blobstore_filename)

        # BlobstoreDownloadHandler serves the file from Google Cloud Storage to
        # your computer using blob_key.
        self.send_blob(blob_key)


app = webapp2.WSGIApplication(
    [
        ("/", CreateAndReadFileHandler),
        ("/blobstore/read", CreateAndReadFileHandler),
        ("/blobstore/serve", CreateAndServeFileHandler),
    ],
    debug=True,
)

Usar BlobReader

Una aplicación puede leer datos de valores de Blobstore mediante una interfaz similar a un objeto file de Python. Esta interfaz puede empezar a leer un valor en cualquier posición de byte y usa varias llamadas de servicio y almacenamiento en búfer, por lo que una aplicación puede acceder al tamaño completo del valor a pesar del límite de tamaño de una sola respuesta de llamada de servicio.

La clase BlobReader puede tomar uno de estos tres valores como argumento de su constructor:

El objeto implementa los métodos de archivo conocidos para leer el valor. La aplicación no puede modificar el valor de Blobstore; los métodos de archivo para escribir no se han implementado.

# Instantiate a BlobReader for a given Blobstore blob_key.
blob_reader = blobstore.BlobReader(blob_key)

# Instantiate a BlobReader for a given Blobstore blob_key, setting the
# buffer size to 1 MB.
blob_reader = blobstore.BlobReader(blob_key, buffer_size=1048576)

# Instantiate a BlobReader for a given Blobstore blob_key, setting the
# initial read position.
blob_reader = blobstore.BlobReader(blob_key, position=0)

# Read the entire value into memory. This may take a while depending
# on the size of the value and the size of the read buffer, and is not
# recommended for large values.
blob_reader_data = blob_reader.read()

# Write the contents to the response.
self.response.headers["Content-Type"] = "text/plain"
self.response.write(blob_reader_data)

# Set the read position back to 0, then read and write 3 bytes.
blob_reader.seek(0)
blob_reader_data = blob_reader.read(3)
self.response.write(blob_reader_data)
self.response.write("\n")

# Set the read position back to 0, then read and write one line (up to
# and including a '\n' character) at a time.
blob_reader.seek(0)
for line in blob_reader:
    self.response.write(line)

Crear solicitudes asíncronas

Una aplicación puede llamar a algunas funciones de Blobstore que funcionan en segundo plano. Blobstore lleva a cabo la solicitud mientras la aplicación hace otras cosas. Para hacer la solicitud, la aplicación llama a una función asíncrona. La función devuelve inmediatamente un objeto RPC, que representa la solicitud. Cuando la aplicación necesita el resultado de la solicitud, llama al método get_result() del objeto RPC.

Si el servicio no ha completado la solicitud cuando la aplicación llama a get_result(), el método espera hasta que se complete la solicitud (o hasta que se haya alcanzado la fecha límite o se haya producido un error). El método devuelve el objeto de resultado o genera una excepción si se produce un error al llevar a cabo la solicitud. Por ejemplo, este fragmento de código

upload_url = blobstore.create_upload_url('/upload')
slow_operation()
self.response.out.write("""<form action="%s" method="POST"
                           enctype="multipart/form-data">""" % upload_url)

se convierte en

upload_url_rpc = blobstore.create_upload_url_async('/upload')
slow_operation()
upload_url = upload_url_rpc.get_result()
self.response.out.write("""<form action="%s" method="POST"
                           enctype="multipart/form-data">""" % upload_url)

En este ejemplo, la aplicación ejecuta el código slow_operation() al mismo tiempo que Blobstore genera la URL de subida.

Cuotas y límites

El espacio usado para los valores de Blobstore se incluye en la cuota de datos almacenados (facturables). Las entidades de información de blobs del almacén de datos se tienen en cuenta para los límites relacionados con el almacén de datos. Ten en cuenta que Google Cloud Storage es un servicio de pago por uso. Se te cobrará según la hoja de precios de Cloud Storage.

Para obtener más información sobre las cuotas de seguridad de todo el sistema, consulta Cuotas.

Además de las cuotas de seguridad de todo el sistema, se aplican los siguientes límites específicamente al uso del almacén de blobs:

  • El tamaño máximo de los datos de Blobstore que puede leer la aplicación con una llamada a la API es de 32 megabytes.
  • El número máximo de archivos que se pueden subir en una sola publicación de formulario es de 500.