Vista geral da API Blobstore para serviços agrupados antigos

A API Blobstore permite que a sua aplicação publique objetos de dados, denominados blobs,que são muito maiores do que o tamanho permitido para objetos no serviço Datastore. Os BLOBs são úteis para publicar ficheiros grandes, como ficheiros de vídeo ou de imagem, e para permitir que os utilizadores carreguem ficheiros de dados grandes. Os BLOBs são criados carregando um ficheiro através de um pedido HTTP. Normalmente, as suas aplicações fazem isto apresentando um formulário com um campo de carregamento de ficheiros ao utilizador. Quando o formulário é enviado, o Blobstore cria um blob a partir do conteúdo do ficheiro e devolve uma referência opaca ao blob, denominada chave do blob,que pode usar posteriormente para publicar o blob. A aplicação pode publicar o valor do objeto BLOB completo em resposta a um pedido do utilizador ou pode ler o valor diretamente através de uma interface semelhante a um ficheiro de streaming.

Apresentamos o Blobstore

O Google App Engine inclui o serviço Blobstore, que permite que as aplicações forneçam objetos de dados limitados apenas pela quantidade de dados que podem ser carregados ou transferidos através de uma única ligação HTTP. Estes objetos são denominados valores Blobstore ou blobs. Os valores do Blobstore são publicados como respostas dos controladores de pedidos e são criados como carregamentos através de formulários Web. As aplicações não criam dados de objetos binários grandes diretamente. Em vez disso, os objetos binários grandes são criados indiretamente por um formulário Web enviado ou outro pedido HTTP POST. Os valores do Blobstore podem ser publicados para o utilizador ou acedidos pela aplicação numa stream semelhante a um ficheiro, através da API Blobstore.

Para pedir a um utilizador que carregue um valor do Blobstore, a sua aplicação apresenta um formulário Web com um campo de carregamento de ficheiros. A aplicação gera o URL de ação do formulário chamando a API Blobstore. O navegador do utilizador carrega o ficheiro diretamente para o Blobstore através do URL gerado. Em seguida, o Blobstore armazena o blob, reescreve o pedido para conter a chave do blob e transmite-o a um caminho na sua aplicação. Um controlador de pedidos nesse caminho na sua aplicação pode realizar o processamento de formulários adicional.

Para publicar um blob, a sua aplicação define um cabeçalho na resposta de saída e o App Engine substitui a resposta pelo valor do blob.

Não é possível modificar os blobs depois de criados, embora seja possível eliminá-los. Cada blob tem um registo de informações do blob correspondente, armazenado no arquivo de dados, que fornece detalhes sobre o blob, como a hora de criação e o tipo de conteúdo. Pode usar a chave do blob para obter registos de informações do blob e consultar as respetivas propriedades.

Uma aplicação pode ler um valor do Blobstore uma parte de cada vez através de uma chamada da API. O tamanho da parte pode ser até ao tamanho máximo de um valor de retorno da API. Este tamanho é ligeiramente inferior a 32 megabytes, representado em Python pela constante google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE. Uma aplicação não pode criar nem modificar valores do Blobstore, exceto através de ficheiros carregados pelo utilizador.

Usar o Blobstore

As aplicações podem usar o Blobstore para aceitar ficheiros grandes como carregamentos dos utilizadores e para publicar esses ficheiros. Os ficheiros são denominados blobs assim que são carregados. As aplicações não acedem diretamente aos blobs. Em alternativa, as aplicações funcionam com blobs através de entidades de informações de blobs (representadas pela classe BlobInfo) no arquivo de dados.

O utilizador cria um objeto BLOB enviando um formulário HTML que inclui um ou mais campos de entrada de ficheiros. A sua aplicação chama create_upload_url() para obter o destino (ação) deste formulário, transmitindo à função um caminho de URL de um controlador na sua aplicação. Quando o utilizador envia o formulário, o navegador do utilizador carrega os ficheiros especificados diretamente para o Blobstore. O Blobstore reescreve o pedido do utilizador e armazena os dados do ficheiro carregado, substituindo os dados do ficheiro carregado por uma ou mais chaves de blob correspondentes e, em seguida, transmite o pedido reescrito ao controlador no caminho do URL que forneceu a create_upload_url(). Este controlador pode fazer processamento adicional com base na chave do objeto binário grande.

A aplicação pode ler partes de um valor do Blobstore através de uma interface de streaming semelhante a um ficheiro. Consulte o artigo A turma BlobReader.

Carregar um blob

Para criar e carregar um blob, siga este procedimento:

1. Crie um URL de carregamento

Chame blobstore.create_upload_url() para criar um URL de carregamento para o formulário que o utilizador vai preencher, transmitindo o caminho da aplicação a carregar quando o POST do formulário estiver concluído.

upload_url = blobstore.create_upload_url("/upload_photo")

Existe uma versão assíncrona, create_upload_url_async(). Permite que o código da sua aplicação continue a ser executado enquanto o Blobstore gera o URL de carregamento.

2. Crie um formulário de carregamento

O formulário tem de incluir um campo de carregamento de ficheiros e o enctype do formulário tem de estar definido como multipart/form-data. Quando o utilizador envia o formulário, o POST é processado pela API Blobstore, que cria o blob. A API também cria um registo de informações para o blob e armazena o registo no arquivo de dados, e passa o pedido reescrito para a sua aplicação no caminho indicado como uma chave 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
            )
        )

Tem de publicar a página do formulário com um Content-Type de text/html; charset=utf-8, ou quaisquer nomes de ficheiros com carateres que não sejam ASCII vão ser mal interpretados.
Este é o tipo de conteúdo predefinido no webapp e no webapp2, mas tem de se lembrar de o fazer se definir o seu próprio Content-Type ou não estiver a usar o webapp.

Não pode usar um Application Load Balancer externo global com um NEG sem servidor para processar pedidos de carregamento enviados para o URL /_ah/upload/ devolvido pela chamada blobstore.create_upload_url. Em alternativa, tem de encaminhar esses pedidos de carregamento diretamente para o serviço do App Engine. Pode fazê-lo através do domínio appspot.com ou de um domínio personalizado mapeado diretamente para o serviço do App Engine.

3. Implemente o controlador de carregamento

Neste controlador, pode armazenar a chave do objeto BLOB com o resto do modelo de dados da sua aplicação. A própria chave do objeto BLOB permanece acessível a partir da entidade de informações do objeto BLOB no arquivo de dados. Tenha em atenção que, depois de o utilizador enviar o formulário e o seu controlador ser chamado, o objeto binário grande já foi guardado e as informações do objeto binário grande foram adicionadas ao repositório de dados. Se a sua aplicação não quiser manter o blob, deve eliminá-lo imediatamente para evitar que fique órfão:

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

A framework de apps Web fornece a classe de processamento de carregamentos blobstore_handlers.BlobstoreUploadHandler para ajudar a analisar os dados do formulário. Para mais informações, consulte a referência para BlobstoreUploadHandler.

Quando o Blobstore reescreve o pedido do utilizador, os corpos das partes MIME dos ficheiros carregados são esvaziados e a chave do blob é adicionada como um cabeçalho de parte MIME. Todos os outros campos e partes do formulário são preservados e transmitidos ao controlador de carregamento. Se não especificar um tipo de conteúdo, o Blobstore tenta inferi-lo a partir da extensão do ficheiro. Se não for possível determinar um tipo de conteúdo, é atribuído o tipo de conteúdo application/octet-stream ao blob recém-criado.

Publicar um blob

Para publicar blobs, tem de incluir um controlador de transferência de blobs como um caminho na sua aplicação. A aplicação publica um objeto BLOB definindo um cabeçalho na resposta de saída. O exemplo seguinte usa a framework webapp. Quando usar webapp, o controlador deve transmitir a chave de blob para o blob pretendido a self.send_blob(). Neste exemplo, a chave do blob é transmitida ao controlador de transferência como parte do URL. Na prática, o controlador de transferência pode obter a chave do blob através de qualquer meio que escolher, como outro método ou ação do utilizador.

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)

A framework de apps Web fornece a classe de controlador de transferência blobstore_handlers.BlobstoreDownloadHandler para ajudar a analisar os dados do formulário. Para mais informações, consulte a referência para BlobstoreDownloadHandler.

Os blobs podem ser publicados a partir de qualquer URL de aplicação. Para publicar um objeto BLOB na sua aplicação, coloca um cabeçalho especial na resposta que contém a chave do objeto BLOB. O App Engine substitui o corpo da resposta pelo conteúdo do blob.

Intervalos de bytes de BLOB

O Blobstore suporta a publicação de parte de um valor grande em vez do valor completo em resposta a um pedido. Para publicar um valor parcial, inclua o cabeçalho X-AppEngine-BlobRange na resposta de saída. O seu valor é um intervalo de bytes HTTP padrão. A numeração de bytes baseia-se em zero. Um X-AppEngine-BlobRange em branco indica à API que ignore o cabeçalho de intervalo e disponibilize o blob completo. Alguns exemplos de intervalos:

  • 0-499 publica os primeiros 500 bytes do valor (bytes 0 a 499, inclusive).
  • 500-999 serve 500 bytes a partir do 501.º byte.
  • 500- serve todos os bytes a partir do 501.º byte até ao final do valor.
  • -500 apresenta os últimos 500 bytes do valor.

Se o intervalo de bytes for válido para o valor do Blobstore, o Blobstore envia um código de estado 206 Partial Content e o intervalo de bytes pedido ao cliente. Se o intervalo não for válido para o valor, o Blobstore envia 416 Requested Range Not Satisfiable.

O Blobstore não suporta vários intervalos de bytes num único pedido (por exemplo, 100-199,200-299), quer se sobreponham ou não.

A classe webapp.blobstore_handlers.BlobstoreDownloadHandler inclui funcionalidades para definir este cabeçalho através de índices de bytes fornecidos e para derivar automaticamente o intervalo de bytes de um cabeçalho range fornecido pelo utilizador.

Aplicação de exemplo completa

Na seguinte aplicação de exemplo, o URL principal da aplicação carrega o formulário que pede ao utilizador um ficheiro para carregar, e o controlador de carregamento chama imediatamente o controlador de transferência para publicar os dados. Isto destina-se a simplificar a aplicação de exemplo. Na prática, provavelmente, não usaria o URL principal para pedir dados de carregamento nem publicaria imediatamente um blob que acabou de carregar.

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 o serviço Images com o Blobstore

O serviço Images pode usar um valor Blobstore como origem de uma transformação. A imagem de origem pode ter o tamanho máximo de um valor do Blobstore. O serviço Images continua a devolver a imagem transformada à aplicação, pelo que a imagem transformada tem de ter menos de 32 megabytes. Isto é útil para criar imagens de miniaturas de fotografias grandes carregadas pelos utilizadores.

Para obter informações sobre a utilização do serviço Images com valores Blobstore, consulte a documentação do serviço Images.

Usar a API Blobstore com o Google Cloud Storage

Pode usar a API Blobstore para armazenar blobs no Cloud Storage em vez de os armazenar no Blobstore. Tem de configurar um contentor, conforme descrito na documentação do Google Cloud Storage, e especificar o contentor e o nome do ficheiro no parâmetro blobstore.blobstore.create_upload_url gs_bucket_name. No controlador de carregamento, tem de processar os metadados FileInfo devolvidos e armazenar explicitamente o nome do ficheiro do Google Cloud Storage necessário para obter o objeto binário grande mais tarde.

Também pode publicar objetos do Cloud Storage através da API Blobstore. Os seguintes fragmentos de código mostram como o fazer:

# 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 o BlobReader

Uma aplicação pode ler dados de valores do Blobstore através de uma interface semelhante a um objeto file do Python. Esta interface pode começar a ler um valor em qualquer posição de bytes e usa várias chamadas de serviço e armazenamento em buffer, para que uma aplicação possa aceder ao tamanho total do valor, apesar do limite no tamanho de uma única resposta de chamada de serviço.

A classe BlobReader pode assumir um de três valores como argumento para o respetivo construtor:

O objeto implementa os métodos de ficheiros familiares para ler o valor. A aplicação não pode modificar o valor do Blobstore. Os métodos de ficheiros para escrita não estão implementados.

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

Fazer pedidos assíncronos

Uma aplicação pode chamar algumas funções do Blobstore que funcionam em segundo plano. O Blobstore executa o pedido enquanto a aplicação faz outras coisas. Para fazer o pedido, a aplicação chama uma função assíncrona. A função devolve imediatamente um objeto RPC; este objeto representa o pedido. Quando a aplicação precisa do resultado do pedido, chama o método get_result() do objeto RPC.

Se o serviço não tiver concluído o pedido quando a aplicação chama get_result(), o método aguarda até que o pedido esteja concluído (ou tenha atingido o prazo ou ocorra um erro). O método devolve o objeto de resultado ou gera uma exceção se ocorrer um erro durante a execução do pedido. Por exemplo, este fragmento do 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)

passa a

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)

Neste exemplo, a aplicação executa o código slow_operation() ao mesmo tempo que o Blobstore gera o URL de carregamento.

Quotas e limites

O espaço usado para valores do Blobstore contribui para a quota de dados armazenados (faturáveis). As entidades de informações de blobs no armazenamento de dados contam para os limites relacionados com o armazenamento de dados. Tenha em atenção que o Google Cloud Storage é um serviço pago. Ser-lhe-á cobrado o valor de acordo com a folha de preços do Cloud Storage.

Para mais informações sobre as quotas de segurança ao nível do sistema, consulte o artigo Quotas.

Além das quotas de segurança ao nível do sistema, os seguintes limites aplicam-se especificamente à utilização do Blobstore:

  • O tamanho máximo dos dados do Blobstore que podem ser lidos pela aplicação com uma chamada API é de 32 megabytes.
  • O número máximo de ficheiros que podem ser carregados num único POST de formulário é 500.