以前のバンドル サービス用の Blobstore API の概要

Blobstore API を使用すると、blob と呼ばれるデータ オブジェクトをアプリケーションで提供できるようになります。blob は、Datastore サービスのオブジェクトに許可されているサイズよりはるかに大きいサイズのオブジェクトです。blob は、動画や画像などのサイズの大きいファイルを提供する場合や、ユーザーがサイズの大きいデータファイルをアップロードする場合に便利です。blob は HTTP リクエストでファイルをアップロードすることによって作成されます。通常アプリケーションでこの処理を行うには、ファイル アップロード用のフィールドを含むフォームをユーザーに提示します。フォームが送信されると、Blobstore によってファイルの内容から blob が作成され、blob への不透明な参照(blob キー)が返されます。このキーは後で blob を提供するときに使用できます。アプリケーションは、ユーザー リクエストに応じて完全な blob 値を提供でき、あるいはファイルに似たストリーミング インターフェースを使用して、値を直接読み取ることができます。

Blobstore を導入する

Google App Engine には Blobstore サービスが含まれています。このサービスを利用すると、アプリケーションでデータ オブジェクトを提供できます。制限は 1 回の HTTP 接続でアップロードまたはダウンロードできるデータ量のみです。これらのオブジェクトを Blobstore 値または blob と呼びます。Blobstore 値はリクエスト ハンドラからのレスポンスとして提供され、ウェブフォームを通じたアップロード データとして作成されます。blob データは、アプリケーションで直接作成されるのではなく、送信されたウェブフォームや他の HTTP POST リクエストによって間接的に作成されます。Blobstore API を使用すると、Blobstore 値をユーザーに送信することや、ファイルに似たストリームでアプリケーションから Blobstore 値にアクセスできます。

アプリケーションで Blobstore 値のアップロードをユーザーに促すには、ファイル アップロード用のフィールドを含むウェブフォームを提示します。アプリケーションは、Blobstore API を呼び出してフォームのアクション URL を生成します。ユーザーのブラウザは、生成された URL を通じてファイルを Blobstore に直接アップロードします。次に、Blobstore は blob を保存し、blob キーが含まれるようにリクエストを書き換えて、アプリケーションのパスに渡します。そのパスにはアプリケーションのリクエスト ハンドラがあり、そこで追加のフォーム処理を行うことができます。

blob を提供するには、アプリケーションで送信レスポンスにヘッダーを設定します。このレスポンスは App Engine で blob 値に置き換えられます。

作成した blob は変更できませんが、削除することはできます。各 blob には対応する blob 情報レコードがあり、データストアに保存されています。blob 情報レコードを参照すると、作成日時やコンテンツ タイプなどの blob の詳細がわかります。blob キーを使用して blob 情報レコードをフェッチし、そのプロパティへのクエリができます。

アプリケーションでは API を呼び出して Blobstore 値を読み取れますが、一度に読み取れるサイズは制限されています。制限サイズは API の戻り値の最大サイズで 32 MB より少し小さい値です。Python では、これは定数 google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE で表されます。アプリケーションでは、ファイルをアップロードするという方法以外で Blobstore 値を作成または変更できません。

Blobstore を使用する

Blobstore を使用すると、ユーザーからアップロードされるサイズの大きなファイルをアプリケーションで受け取ることや提供することができます。アップロードされたファイルは blob と呼ばれます。アプリケーションは blob に直接アクセスするのではなく、データストアの blob 情報エンティティBlobInfo クラスで表されます)を介して blob を使用します。

ユーザーは 1 つ以上のファイル入力フィールドを含む HTML フォームを送信して blob を作成します。アプリケーションは create_upload_url() を呼び出してこのフォームの送信先(action)を取得し、この関数にアプリケーションのハンドラの URL パスを渡します。ユーザーがフォームを送信すると、指定されたファイルがユーザーのブラウザから Blobstore に直接アップロードされます。Blobstore はユーザーのリクエストを書き換え、アップロードされたファイルデータを保存します。その際、アップロードされたファイルデータを 1 つ以上の対応する blob キーで置き換えます。その後、書き換えたリクエストを、create_upload_url() に指定された URL パスにあるハンドラに渡します。このハンドラは、blob キーに基づいて追加の処理を行えます。

アプリケーションは、ファイルに似たストリーミング インターフェースを使用して、Blobstore 値の一部を読み取ることができます。BlobReader クラスをご覧ください。

blob をアップロードする

blob を作成してアップロードする手順は次のとおりです。

1. アップロード URL を作成する

blobstore.create_upload_url() を呼び出して、ユーザーが記入するフォームのアップロード URL を作成し、そのフォームの POST が完了したときに読み込むアプリケーション パスを渡します。

upload_url = blobstore.create_upload_url("/upload_photo")

非同期バージョンの create_upload_url_async() があります。このバージョンでは、Blobstore がアップロード URL を生成している間も、アプリケーション コードを実行し続けることができます。

2. アップロード フォームを作成する

フォームにはファイル アップロード用のフィールドを含め、フォームの enctypemultipart/form-data と設定する必要があります。ユーザーがフォームを送信すると、Blobstore API が POST を処理し、blob を作成します。この 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 を削除して 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 パーツの本文が空になり、MIME パーツのヘッダーとして blob キーが追加されます。他のすべてのフォーム フィールドとパーツは保持され、アップロード ハンドラに渡されます。コンテンツ タイプを指定していない場合、Blobstore はファイル拡張子から推定を試みます。コンテンツ タイプが判断できない場合、新しく作成される blob にはコンテンツ タイプ application/octet-stream が割り当てられます。

blob を提供する

blob を提供するには、blob のダウンロード ハンドラをパスとしてアプリケーションに含める必要があります。アプリケーションは送信するレスポンスにヘッダーを設定することによって、blob を提供します。次のサンプルでは、webapp フレームワークを使用します。webapp を使用する場合、ヘッダーから該当の blob の blob キーを self.send_blob() に渡す必要があります。次の例では、blob キーは URL の一部としてダウンロード ハンドラに渡されています。実際のダウンロード ハンドラでは、他のメソッドやユーザーの操作など任意の手段で 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 は、任意のアプリケーション URL から提供できます。アプリケーションで blob を提供するには、blob キーを含むレスポンスに特別なヘッダーを配置します。App Engine はレスポンスの本文を blob のコンテンツに置き換えます。

blob バイトの範囲

Blobstore は、リクエストへのレスポンスとして、サイズの大きな値全体ではなく値の一部の提供をサポートしています。値の一部を提供するには、送信レスポンスに X-AppEngine-BlobRange ヘッダーを含めます。その値は標準的な HTTP バイト範囲とします。バイト番号は 0 から始まります。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 では、重複の有無にかかわらず 1 回のリクエストでの複数のバイト範囲の指定(100-199,200-299 など)はサポートしていません。

webapp.blobstore_handlers.BlobstoreDownloadHandler クラスには、指定されたバイト インデックスを使用してこのヘッダーを設定する機能と、ユーザー指定の range ヘッダーから自動的にバイト範囲を導出する機能が含まれています。

サンプル アプリケーションを完成させる

以下のサンプル アプリケーションでは、アプリケーションのメイン URL でユーザーにファイルのアップロードを求めるフォームを読み込みます。アップロード ハンドラはすぐにダウンロード ハンドラを呼び出し、データを提供します。これは、サンプル アプリケーションを簡略化するためのものです。実際にはメイン URL を使用してデータのアップロードをリクエストしたり、アップロードした 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,
)

Blobstore で Images サービスを使用する

Images サービスでは、変換のソースとして Blobstore 値を使用できます。Blobstore 値の最大サイズと同サイズのソース画像を使用できます。Images サービスで変換後の画像がアプリケーションに返されるので、そのサイズは 32 MB より小さくなければなりません。これは、ユーザーがアップロードしたサイズの大きな写真のサムネイル画像を作成する場合に便利です。

Images サービスで Blobstore 値を使用する方法については、Images サービスのドキュメントをご覧ください。

Google Cloud Storage で Blobstore API を使用する

Blobstore API を使用して Blobstore ではなく Cloud Storage に blob を保存できます。 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 値からデータを読み取ることができます。このインターフェースでは、任意のバイト位置で値の読み取りを開始できます。また、1 つのサービス呼び出しのレスポンスにサイズ制限があったとしても、アプリケーションが値全体にアクセスできるように、複数のサービス呼び出しとバッファリングを使用します。

BlobReader クラスは、コンストラクタへの引数として次の 3 つのいずれかの値を取ることができます。

このオブジェクトは、値を読み取るために使い慣れたファイル メソッドを実装しています。ただし、アプリケーションは 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 がアップロード URL を生成すると同時に、アプリケーションが slow_operation() コードを実行します。

割り当てと上限

Blobstore 値に使用する領域は、保存データ(課金対象)の割り当て量の計算対象になります。データストアの blob 情報エンティティはデータストア関連の制限に対してカウントされます。Google Cloud Storage は従量課金サービスであり、Cloud Storage の料金表に従って課金されることに注意してください。

システム全体の安全上の割り当て量について詳しくは、割り当てをご覧ください。

Blobstore の使用については特に、システム全体の安全上の割り当て量に加えて次の制限が適用されます。

  • アプリケーションからの API 呼び出し 1 回で読み取ることのできる Blobstore データの最大サイズは 32 MB です。
  • 1 つのフォームの POST でアップロードできるファイルの最大数は 500 です。