适用于旧版捆绑服务的 Blobstore API 概览

借助 Blobstore API,您的应用可以提供称为 Blob 的数据对象,这些数据对象的大小远远超过 Datastore 服务允许的上限。Blob 对于提供大型文件(如视频或图片文件)非常有用,并且支持用户上传大型数据文件。Blob 是通过 HTTP 请求上传文件而创建的。通常,您的应用会通过向用户显示包含文件上传字段的表单来完成此操作。在提交表单后,Blobstore 根据该文件的内容创建一个 Blob,并返回对该 Blob 的不透明引用,称为“Blob 键”,您此后可使用该键提供 Blob。应用可以响应用户请求提供完整的 Blob 值,也可以使用类似流文件的接口直接读取该值。

Blobstore 简介

Google App Engine 包含 Blobstore 服务,借助此服务,应用提供的数据对象仅受可通过单个 HTTP 连接上传或下载的数据量限制。这些对象称为“Blobstore 值”或“Blob”。Blobstore 值作为请求处理程序的响应提供,以通过 Web 表单上传内容的方式创建。应用不会直接创建 Blob 数据;而是通过提交的网络表单或其他 HTTP POST 请求间接创建 Blob。Blobstore 值可以使用 Blobstore API 提供给用户,也可以由应用在类文件流中访问。

为了提示用户上传 Blobstore 值,应用提供带有文件上传字段的 Web 表单。应用通过调用 Blobstore API 来生成表单的操作网址。用户的浏览器通过生成的网址将文件直接上传到 Blobstore。然后,Blobstore 会存储 blob,重写入请求以包含 blob 键,并将其传递给应用中的路径。位于应用中该路径的请求处理程序可以执行其他表单处理。

为了传送 Blob,您的应用将为传出响应设置标头,并且 App Engine 会使用 Blob 值替换该响应。

Blob 一旦创建便无法修改,但可以删除。每个 Blob 都具有相应的 Blob 信息记录(存储在数据存储区中),该记录提供有关 Blob 的详细信息(例如,创建时间和内容类型)。您可以使用 blob 键提取 blob 信息记录以及查询其属性。

应用可以使用 API 调用一次读取 Blobstore 值的一部分。读取部分的大小不得超过 API 返回值的大小上限。此大小略小于 32 兆字节,在 Java 中以常量 google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE 表示。应用只能通过用户上传的文件来创建或修改 Blobstore 值。

使用 Blobstore

应用可以使用 Blobstore 来接收用户上传的较大文件并提供这些文件。文件上传后,将称为 blob。应用不直接访问 blob,而是通过数据存储区中的 blob 信息实体(由 BlobInfo 类表示)处理 blob。

用户通过提交包含一个或多个文件输入字段的 HTML 表单来创建 Blob。应用调用 create_upload_url() 来获取此表单的目标(操作),并将应用中处理程序的网址路径传递给函数。当用户提交表单时,用户的浏览器直接将指定文件上传到 Blobstore。Blobstore 重新编写用户的请求并存储上传的文件数据(将上传的文件数据替换为一个或多个相应的 Blob 键),然后将重新编写的请求传递给向 create_upload_url() 提供的网址路径处的处理程序。该处理程序会根据 blob 键进行额外的处理。

应用可以使用类文件流式接口读取 Blobstore 值的片段。请参阅 BlobReader

上传 blob

如需创建和上传 Blob,请执行以下步骤:

1. 创建上传网址

调用 blobstore.create_upload_url() 为用户将要填写的表单创建一个上传网址,并在表单的 POST 操作完成后传递要加载的应用路径。

upload_url = blobstore.create_upload_url("/upload_photo")

有一个异步版本 create_upload_url_async()。它允许您的应用代码在 Blobstore 生成上传网址时继续运行。

2. 创建上传表单

表单必须包含文件上传字段,并且必须将表单的 enctype 设置为 multipart/form-data。当用户提交表单时,POST 由创建 Blob 的 Blobstore API 处理。该 API 还会为 Blob 创建信息记录,并将该记录存储在数据存储区中,然后将重写后的请求传递到应用的指定路径作为 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. 实现上传处理程序

在此处理程序中,您可以将 Blob 键与应用数据模型的其他部分存储在一起。Blob 键本身仍然可以通过数据存储区中的 Blob 信息实体进行访问。请注意,用户提交表单并调用处理程序后,即已保存 Blob,并且 Blob 信息已添加到数据存储区中。如果应用不再需要 Blob,您应立即删除 Blob 以防止其成为孤立文件:

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

webapp 框架提供了 blobstore_handlers.BlobstoreUploadHandler 上传处理程序类,以帮助您解析表单数据。如需了解详情,请参阅 BlobstoreUploadHandler 的相关参考。

当 Blobstore 重写用户的请求时,上传文件的 MIME 部分将清空其正文,并且 Blob 键会添加为 MIME 部分标头。所有其他表单字段和部分都会予以保留,并传递给上传处理程序。如果您没有指定内容类型,Blobstore 会尝试根据文件扩展名推断内容类型。如果无法确定内容类型,则系统会为新创建的 Blob 指定 application/octet-stream 内容类型。

传送 Blob

为了提供 blob,您必须以应用中路径的形式包含 blob 下载处理程序。应用通过在传出响应上设置标头来提供 Blob。该示例使用 webapp 框架:使用 webapp 时,处理程序应将所需 blob 的 blob 键传递给 self.send_blob()。在此示例中,blob 键会作为网址的一部分传递给下载处理程序。实际上,下载处理程序可通过您选择的任意方式获取 Blob 键,例如,通过其他方法或用户操作。

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)

webapp 框架提供了 blobstore_handlers.BlobstoreDownloadHandler 下载处理程序类,帮助您解析表单数据。如需了解详情,请参阅 BlobstoreDownloadHandler 的相关参考。

Blob 可以从任何应用网址传递。如需在应用中传送 Blob,请在包含 Blob 键的响应中添加一个特殊标头。App Engine 会将响应正文替换为 Blob 内容。

Blob 字节范围

Blobstore 支持提供大值的一部份来响应请求,而不是提供完整的值。如需提供部分值,请在传出响应中包含 X-AppEngine-BlobRange 标头。其值是标准的 HTTP 字节范围。字节编号从零开始。空 X-AppEngine-BlobRange 指示 API 忽略范围标头并提供完整 Blob。示例范围包括:

  • 0-499 提供值的前 500 个字节(字节 0 到 499,含边界值)。
  • 500-999 提供从第 501 个字节开始的 500 个字节。
  • 500- 提供从第 501 个字节开始到值结束的所有字节。
  • -500 提供值的最后 500 个字节。

如果字节范围对 Blobstore 值有效,Blobstore 将向客户端发送 206 Partial Content 状态代码和请求的字节范围。如果范围对该值无效,Blobstore 将发送 416 Requested Range Not Satisfiable

Blobstore 不支持单个请求中的多个字节范围(例如 100-199,200-299),不论字节范围是否重叠都是如此。

webapp.blobstore_handlers.BlobstoreDownloadHandler 类包含使用提供的字节索引设置此标头的功能,以及从用户提供的 range 标头自动派生字节范围的功能。

完整示例应用

在以下示例应用中,应用的主网址会加载要求用户上传文件的表单,而且上传处理程序会立即调用下载处理程序来提供数据。这样做的目的是简化示例应用。实际上,您可能不会使用主网址请求上传数据,也不会立即处理刚上传的 Blob。

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

将 Google 图片服务与 Blobstore 搭配使用

图片服务可以将 Blobstore 值作为转换的源数据。源图片的大小可以达到 Blobstore 值的大小上限。图片服务仍然将转换后的图片返回给应用,因此转换后的图片必须小于 32 兆字节。这对于针对用户上传的较大照片创建缩略图非常有用。

如需了解如何将图片服务与 Blobstore 值搭配使用,请参阅图片服务文档

将 Blobstore API 与 Google Cloud Storage 搭配使用

您可以使用 Blobstore API 将 blob 存储到 Cloud Storage 中,而不是存储到 Blobstore 中。您需要按照 Google Cloud Storage 文档中的说明设置存储桶,并在 blobstore.blobstore.create_upload_url gs_bucket_name 参数中指定该存储桶和文件名。在上传处理程序中,您需要处理返回的 FileInfo 元数据,并明确存储后续检索 Blob 所需的 Google Cloud Storage 文件名。

您还可以使用 Blobstore API 提供 Cloud Storage 对象。以下代码段演示了如何执行此操作:

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

使用 BlobReader

应用可以使用类似于 Python file 对象的接口从 Blobstore 值读取数据。此类接口可以在任何字节位置开始读取值,并使用多个服务调用和缓冲,因此应用可以访问完整大小的值,尽管单个服务调用的响应大小有限。

BlobReader 类可以接受以下三个值之一作为其构造函数的参数:

该对象采用常用的文件方法读取值。应用无法修改 Blobstore 值;用于写入的文件方法尚未实现。

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

执行异步请求

应用可以调用在后台运行的部分 Blobstore 函数。Blobstore 在应用执行其他操作时执行请求。为了发出请求,应用将调用异步函数。该函数将立即返回一个 RPC 对象,此对象代表相应请求。如果应用需要请求的结果,将会调用 RPC 对象的 get_result() 方法。

如果应用调用 get_result() 时服务尚未完成请求,则该方法将等待,直到请求完成(或时限已到,或发生错误)。该方法返回结果对象;如果执行请求时发生错误,则引发异常。例如,以下代码段

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

将变为:

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)

在上述示例中,应用在 Blobstore 生成上传网址的同时执行 slow_operation() 代码。

配额和限制

Blobstore 值中的空格会占用存储的数据量(计费)配额。数据存储区中的 blob 信息实体会计入数据存储区相关的限制。请注意,Google Cloud Storage 是付费服务;您需要按照 Cloud Storage 价格表支付费用。

如需详细了解系统范围的安全配额,请参阅配额

除系统范围的安全配额外,以下限制特别适用于 Blobstore 的使用:

  • 应用单次调用 API 可读取的 Blobstore 数据的大小上限为 32 兆字节。
  • 表单的单次 POST 操作最多可以上传 500 个文件。