Présentation de l'API Blobstore pour Python

Remarque : Nous vous recommandons d'utiliser Google Cloud Storage plutôt que Blobstore pour stocker les données de blobs.

L'API Blobstore permet à votre application de diffuser des objets de données, appelés blobs, beaucoup plus volumineux que la taille autorisée pour les objets dans le service Datastore. Les blobs sont particulièrement utiles pour la diffusion de fichiers de grande taille, tels que des fichiers vidéo ou image, ainsi que pour le transfert de fichiers de données volumineux. Ils sont créés lorsqu'un fichier est importé via une requête HTTP. Pour ce faire, vos applications présentent généralement à l'utilisateur un formulaire avec un champ d'importation de fichier. Une fois ce formulaire envoyé, le Blobstore crée un blob à partir du contenu du fichier et renvoie une référence opaque au blob, appelée clé blob, que vous pouvez ensuite utiliser pour diffuser le blob. L'application peut diffuser la valeur complète du blob en réponse à une requête utilisateur. Elle peut aussi lire directement la valeur à l'aide d'une interface de streaming de type fichier.

Présentation du service Blobstore

Google App Engine inclut le service Blobstore, qui permet aux applications de diffuser des objets de données limités uniquement par le volume de données pouvant être importé ou téléchargé via une connexion HTTP. Ces objets sont appelés valeurs Blobstore ou blobs. Les valeurs Blobstore sont diffusées en tant que réponses par des gestionnaires de requêtes et créées sous forme de transferts via des formulaires Web. Les applications ne créent pas directement de données blob. Les blobs sont générés indirectement par le biais d'un formulaire Web ou d'une autre requête HTTP POST. Les valeurs Blobstore peuvent être envoyées à l'utilisateur ou lues par l'application dans un flux de type fichier à l'aide de l'API Blobstore.

Pour inviter un utilisateur à importer une valeur Blobstore, votre application lui présente un formulaire Web comprenant un champ d'importation de fichier. L'application génère l'URL d'action du formulaire en appelant l'API Blobstore. Le navigateur de l'utilisateur importe le fichier directement dans le Blobstore via l'URL générée. Le service Blobstore stocke ensuite le blob, réécrit la requête de manière à ce qu'elle contienne la clé blob et la transmet à un chemin dans votre application. Si un gestionnaire de requêtes est situé à ce chemin d'accès, il peut effectuer d'autres opérations de traitement sur le formulaire.

Pour diffuser un blob, votre application définit un en-tête sur la réponse sortante, et App Engine remplace la réponse par la valeur du blob.

Une fois créés, les blobs ne peuvent pas être modifiés. Cependant, ils peuvent être supprimés. Chaque blob est associé à un enregistrement d'informations stocké dans le datastore. Cet enregistrement fournit des détails tels que la date de création et le type de contenu du blob. Vous pouvez utiliser la clé blob pour récupérer des enregistrements d'informations sur les blobs et interroger leurs propriétés.

Une application peut lire une valeur Blobstore portion par portion, à l'aide d'un appel d'API. La taille de chaque portion est limitée par la taille maximale d'une valeur renvoyée par l'API. Cela correspond à un peu moins de 32 Mo et à la constante google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE dans Python. Une application ne peut créer ou modifier des valeurs Blobstore que par le biais de fichiers importés par l'utilisateur.

Utiliser le service Blobstore

Les applications peuvent recourir au service Blobstore pour accepter l'importation de fichiers volumineux de la part des utilisateurs et pour les diffuser. Une fois importés, ces fichiers sont appelés blobs. Les applications n'accèdent pas directement aux blobs. Elles fonctionnent plutôt via des entités d'informations de blobs (représentées par la classe BlobInfo) dans le datastore.

L'utilisateur crée un blob en soumettant un formulaire HTML qui inclut un ou plusieurs champs de saisie de fichiers. Votre application définit create_upload_url() comme destination (action) de ce formulaire, en transmettant à la fonction le chemin d'URL d'un gestionnaire dans votre application. Lorsque l'utilisateur envoie le formulaire, son navigateur transfère directement les fichiers indiqués au service Blobstore. Le service réécrit la demande de l'utilisateur et stocke les données du fichier importé, en remplaçant les données du fichier importé par une ou plusieurs clés blob correspondantes, puis transmet la demande réécrite au gestionnaire au chemin d'URL que vous avez fourni à create_upload_url(). Ce gestionnaire peut procéder à un traitement supplémentaire en fonction de la clé blob.

L'application peut lire des portions d'une valeur Blobstore à l'aide d'une interface de diffusion en continu de type fichier. Consultez la section consacrée à la classe BlobReader.

Importer un blob

Pour créer et importer un blob, procédez comme suit :

1. Créez une URL d'importation

Appelez blobstore.create_upload_url() pour créer l'URL d'importation du formulaire à remplir par l'utilisateur, en indiquant le chemin de l'application à charger une fois le formulaire envoyé à l'aide de la méthode POST.

upload_url = blobstore.create_upload_url('/upload_photo')

Il existe une version asynchrone, create_upload_url_async(). Elle permet au code de votre application de continuer à s'exécuter pendant que Blobstore génère l'URL d'importation.

2. Créez un formulaire d'importation

Le formulaire doit comprendre un champ d'importation de fichier, et son type enctype doit être défini sur multipart/form-data. Lorsque l'utilisateur soumet le formulaire, la requête POST est traitée par l'API Blobstore, ce qui entraîne la création du blob. L'API crée également un enregistrement d'informations pour le blob et le stocke dans le datastore. Elle transmet ensuite la requête réécrite à votre application au chemin fourni en tant que clé 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))

3. Mettez en œuvre le gestionnaire d'importation

Dans ce gestionnaire, vous pouvez stocker la clé blob avec le reste du modèle de données de l'application. La clé blob elle-même reste accessible à partir de l'entité d'informations du blob dans le datastore. Notez qu'une fois que l'utilisateur a envoyé le formulaire et que votre gestionnaire a été appelé, le blob a déjà été sauvegardé et les informations associées ajoutées au datastore. Si votre application ne souhaite pas conserver le blob, nous vous conseillons de le supprimer immédiatement pour éviter qu'il ne devienne orphelin :

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())

L'environnement logiciel webapp fournit la classe de gestionnaire d'importation blobstore_handlers.BlobstoreUploadHandler pour vous aider à analyser les données de formulaire. Pour en savoir plus, consultez la documentation de référence sur BlobstoreUploadHandler.

Lorsque le service Blobstore réécrit la requête utilisateur, le corps des parties MIME des fichiers importés est vidé, et la clé blob est ajoutée en tant qu'en-tête de la partie MIME. Tous les autres champs et parties du formulaire sont préservés et transmis au gestionnaire d'importation. Si vous ne spécifiez pas de type de contenu, Blobstore tentera de l'inférer à partir de l'extension du fichier. S'il n'y parvient pas, le blob nouvellement créé se verra attribuer le type de contenu application/octet-stream.

Diffuser un blob

Pour diffuser des blobs, vous devez inclure un gestionnaire de téléchargement de blob en tant que chemin d'accès dans votre application. Cette dernière diffuse un blob en définissant un en-tête sur la réponse sortante. L'exemple suivant utilise l'environnement logiciel webapp. Lors de l'utilisation de webapp, le gestionnaire doit transmettre la clé blob correspondant au blob souhaité à self.send_blob(). Dans cet exemple, la clé blob est transmise au gestionnaire de téléchargement dans l'URL. Dans la pratique, le gestionnaire de téléchargement peut obtenir la clé blob par n'importe quel moyen de votre choix, par exemple via une autre méthode ou action utilisateur.

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)

L'environnement logiciel webapp fournit la classe de gestionnaire de téléchargement blobstore_handlers.BlobstoreDownloadHandler pour vous aider à analyser les données de formulaire. Pour en savoir plus, consultez la documentation de référence sur BlobstoreDownloadHandler.

Les blos peuvent être transmis à partir de n'importe quelle URL d'application. Pour diffuser un blob dans votre application, vous devez mettre un en-tête spécial dans la réponse contenant la clé blob. App Engine remplace le corps de la réponse par le contenu du blob.

Plages d'octets des blobs

Le service Blobstore permet de diffuser une partie plutôt que la totalité d'une grande valeur en réponse à une requête. Pour diffuser une valeur partielle, incluez l'en-tête X-AppEngine-BlobRange dans la réponse sortante. Sa valeur correspond à une plage d'octets HTTP standard. La numérotation des octets est basée sur zéro. Si X-AppEngine-BlobRange est vide, l'API ignore l'en-tête de plage et diffuse l'ensemble du blob. Voici des exemples de plages :

  • 0-499 : diffuse les 500 premiers octets de la valeur (octets 0 à 499 inclus).
  • 500-999 : diffuse 500 octets à partir du 501e octet.
  • 500- : diffuse tous les octets à partir du 501e octet.
  • -500 : diffuse les 500 derniers octets de la valeur.

Si la plage d'octets est valide pour la valeur Blobstore, le service Blobstore renvoie le code d'état 206 Partial Content et la plage d'octets demandée au client. Si la plage n'est pas valide pour la valeur, Blobstore renvoie le code d'état 416 Requested Range Not Satisfiable.

Le service Blobstore n'accepte pas plusieurs plages d'octets dans une même requête (par exemple, 100-199,200-299), qu'elles se chevauchent ou non.

La classe webapp.blobstore_handlers.BlobstoreDownloadHandler comprend des fonctionnalités qui permettent de définir cet en-tête à l'aide d'indices d'octets fournis et de dériver automatiquement la plage d'octets à partir de l'en-tête range fourni par l'utilisateur.

Exemple d'application complète

Dans l'exemple d'application suivant, l'URL principale de l'application charge le formulaire qui demande un fichier à importer à l'utilisateur, et le gestionnaire d'importation appelle immédiatement le gestionnaire de téléchargement pour diffuser les données. Cela permet de simplifier l'exemple d'application. Dans la pratique, il est peu probable que vous utilisiez l'URL principale pour demander l'importation de données, ou encore que vous diffusiez un blob immédiatement après l'avoir importé.

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)

Utiliser le service Images avec le Blobstore

Le service Images peut utiliser une valeur Blobstore comme source d'une transformation. La taille de l'image source est limitée par la taille maximale d'une valeur Blobstore. Le service Images renvoie quand même l'image transformée à l'application. Par conséquent, l'image transformée doit être inférieure à 32 méga-octets. Cela est utile pour créer des vignettes des photos volumineuses transférées par les utilisateurs.

Pour en savoir plus sur l'utilisation du service Images avec des valeurs Blobstore, consultez la documentation sur le service Images.

Utiliser l'API Blobstore avec Google Cloud Storage

Vous pouvez utiliser l'API Blobstore pour stocker des blobs dans Cloud Storage au lieu de les stocker dans Blobstore. Vous devez configurer un bucket comme décrit dans la documentation de Google Cloud Storage, puis spécifier le bucket et le nom du fichier dans le paramètre gs_bucket_name de blobstore.blobstore.create_upload_url. Dans le gestionnaire d'importation, vous devez traiter les métadonnées FileInfo renvoyées et stocker explicitement le nom du fichier Google Cloud Storage nécessaire pour récupérer le blob ultérieurement.

Vous pouvez également diffuser des objets Cloud Storage à l'aide de l'API Blobstore.

"""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)

Utiliser BlobReader

Une application peut lire des données à partir de valeurs Blobstore en utilisant une interface semblable à un objet file dans Python. Cette interface peut commencer à lire une valeur à n'importe quelle position d'octet, et utilise plusieurs appels de service ainsi que la mise en mémoire tampon. L'application peut ainsi accéder à l'intégralité de la valeur malgré la limite de taille d'une réponse à un appel de service.

La classe BlobReader peut utiliser l'une des trois valeurs suivantes comme argument pour son constructeur :

  • Une valeur BlobKey
  • Une valeur BlobKey au format chaîne
  • Un objet BlobInfo

L'objet implémente les méthodes de fichier courantes pour lire la valeur. L'application ne peut pas modifier la valeur Blobstore, car elle ne met pas en œuvre les méthodes de fichier pour l'écriture.

# 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)

Envoyer des requêtes asynchrones

Une application peut appeler certaines fonctions Blobstore qui s'exécutent en arrière-plan. Le service Blobstore traite la requête pendant que l'application effectue d'autres tâches. Pour envoyer la requête, l'application appelle une fonction asynchrone. Celle-ci renvoie immédiatement un objet RPC représentant la requête. Lorsque l'application a besoin du résultat de la requête, elle appelle la méthode get_result() de l'objet RPC.

Si le service n'a pas terminé la requête lorsque l'application appelle get_result(), la méthode attend que la requête soit terminée (ou que le délai ait expiré, ou qu'une erreur se produise). La méthode renvoie l'objet du résultat ou génère une exception si une erreur s'est produite lors de l'exécution de la requête. Par exemple, cet extrait de code

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

devient

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)

Dans cet exemple, l'application exécute le code slow_operation() en même temps que le service Blobstore génère l'URL d'importation.

Quotas et limites

L'espace utilisé pour les valeurs Blobstore est décompté du quota Données stockées (facturables). Les entités d'informations de blobs du datastore sont comptabilisées dans les limites associées au datastore. Notez que Google Cloud Storage est un service payant. La facturation est établie conformément à la grille tarifaire de Cloud Storage.

Pour en savoir plus sur les quotas de sécurité à l'échelle du système, consultez la page Quotas.

Outre les quotas de sécurité à l'échelle du système, les limites suivantes s'appliquent spécifiquement à l'utilisation du service Blobstore :

  • La taille maximale des données Blobstore lisibles par l'application avec un seul appel d'API est de 32 méga-octets.
  • Le nombre maximal de fichiers pouvant être importés via un seul formulaire POST est de 500.
Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Python