webapp Blobstore ハンドラ

webapp には、Blobstore API で使用するためのリクエスト ハンドラ クラスが含まれます。BlobstoreUploadHandler は、Blobstore 経由で BlobInfo レコードに渡されるアップロード リクエストを、以降の処理のために解析するロジックを提供します。BlobstoreDownloadHandler を使用すると、どのパスからでも簡単に Blobstore 値を指定できます。

BlobstoreUploadHandler

Blobstore には、アプリケーションのユーザーまたは管理者によるファイル アップロードを介して値が追加されます。アプリケーションは、ファイル アップロード フィールドと、Blobstore へのアップロードを指示するフォーム アクションを含む、ウェブフォームを送信します。アプリは、関数(create_upload_url())を呼び出し、ユーザーがファイルをアップロードしたときに呼び出されるアプリハンドラの URL をその関数に渡すことにより、フォーム アクションの URL を取得します。webapp アプリケーションは、この URL のハンドラとして BlobstoreUploadHandler クラスのサブクラスを使用できます。

get_uploads() メソッドは、リクエストに含まれるアップロードされたファイルごとに 1 つずつ、BlobInfo オブジェクトのリストを返します。各オブジェクトには、アップロードした値の Blobstore キーの他、ファイル名やサイズなどのメタデータが含まれます。アップロードした各ファイルについては、この情報が含まれる対応するエンティティがデータストア内にもあるため、後で blob キーを指定して BlobInfo オブジェクトを取得したり、メタデータのフィールドでデータストア クエリを実行したりすることができます。アップロード ハンドラは、この情報をデータストアではなく要求データから直接解析します。

デフォルトでは、get_uploads() は、リクエストでアップロードしたすべてのファイルについて BlobInfo オブジェクトを返します。また、このメソッドは field_name 引数を受け取って、特定のファイル アップロード フィールドのファイルのみを取得することもします。戻り値は常にリストで、空のリストの場合もあります。

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

Google Cloud Storage での BlobstoreUploadHandler の使用

Cloud Storage でこのアップロード ハンドラを使用する場合は、完全な Cloud Storage オブジェクト ファイル名を取得して保存する必要があります。これは、Cloud Storage からファイルを再度取得する際に必要となるためです。関数 get_file_infos を使用します。この関数は、それぞれのアップロードに対応する FileInfo レコードのリストを返します。完全な Cloud Storage オブジェクト名、コンテンツ タイプ、作成日時などのデータを FileInfo から取得できます(詳細についてはリンク先をご覧ください)。

BlobstoreDownloadHandler

Blobstore 値を提供するために、アプリケーションは X-AppEngine-BlobKey ヘッダーを文字列形式で Blobstore キーの値に設定します。App Engine では、応答でこのヘッダーを認識すると、応答の本文として blob の値を提供します。webapp ハンドラクラス BlobstoreDownloadHandler により、応答にこの値を簡単に設定できます。

send_blob() メソッドは、BlobKey オブジェクト、文字列キー、または BlobInfoblob_key_or_info 引数として受け取り、blob 値がユーザーに提供されるように応答データを設定します。このメソッドは、保存された blob 値の MIME コンテンツ タイプを上書きする、オプションの content_type 引数を受け取ります。デフォルトで blob は、アップロードしたクライアントによって設定されたコンテンツ タイプ、ファイル名から派生したコンテンツ タイプ、一般的なタイプ(他のタイプ情報が利用できない場合)のいずれかのコンテンツ タイプで提供されます。

send_blob() メソッドは save_as 引数を受け入れます。この引数は、blob データが生の応答データとして送信されるか、ファイル名を持つ MIME 添付ファイルとして送信されるかを決定します。引数が文字列の場合、blob は添付ファイルとして送信され、文字列値がファイル名として使用されます。Trueblob_key_or_infoBlobInfo オブジェクトである場合は、このオブジェクトのファイル名が使用されます。デフォルトでは、blob データは MIME 添付ファイルではなく、応答の本文として送信されます。

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)

Blobstore では、バイト インデックスの範囲として記述することにより、値全体ではなく値の一部のみを送信できます。バイト インデックスの範囲を BlobstoreDownloadHandlersend_blob() メソッドに指定する方法は 2 つあります。1 つ目の方法は、引数 start と引数 end として範囲を指定することです。

            # Send the first 1,000 bytes of the value.
            self.send_blob(key, start=0, end=999)

デフォルトでは、BlobstoreDownloadHandler はリクエストの range ヘッダーを使用します。元の範囲のヘッダーが使用されないようにするには、パラメータ use_range=Falsesend_blob() に指定します。

            # Send the full value of the blob and
            # block the "range" header.
            self.send_blob(key, use_range=False)

range ヘッダーの値は、標準の HTTP バイト範囲です。BlobstoreDownloadHandlerwebob.byterange を使用して、このヘッダー値を解析します。

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

以下のサンプル アプリケーションでは、アプリケーションのメイン 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)