기존 번들 서비스용 Blobstore API 개요

애플리케이션에서 Blobstore API를 사용하면 Datastore 서비스의 객체에 허용되는 크기보다 훨씬 큰 blob이라는 데이터 객체를 제공할 수 있습니다. Blob은 동영상 또는 이미지 파일과 같은 대용량 파일을 제공하고 사용자가 대규모 데이터 파일을 업로드할 수 있도록 하는 데 유용합니다. HTTP 요청을 통해 파일을 업로드하면 blob이 생성됩니다. 일반적으로 애플리케이션은 파일 업로드 필드가 있는 양식을 사용자에게 제공하는 방식으로 이 작업을 수행합니다. 양식이 제출되면 Blobstore는 파일의 콘텐츠로 blob을 만들고 blob에 blob 키라고 하는 비공개 참조를 반환하며, 나중에 이 키를 사용하여 blob을 제공할 수 있습니다. 애플리케이션은 사용자 요청에 응답하여 전체 blob 값을 제공할 수도 있고, 스트리밍 파일과 유사한 인터페이스를 사용하여 직접 값을 읽을 수도 있습니다.

Blobstore 소개

애플리케이션은 Google App Engine에 포함된 Blobstore 서비스를 사용하여 데이터 객체를 제공할 수 있으며, 단일 HTTP 연결을 통해 업로드 또는 다운로드할 수 있는 데이터 양 이외에는 한도가 없습니다. 이러한 객체를 Blobstore 값 또는 blob이라고 합니다. Blobstore 값은 요청 핸들러의 응답으로 제공되며 웹 양식을 통한 업로드로 만들어집니다. 애플리케이션은 blob 데이터를 직접 만들지 않습니다. 대신 blob은 제출된 웹 양식 또는 기타 HTTP POST 요청에 의해 간접적으로 생성됩니다. Blobstore API를 사용하여 Blobstore 값을 사용자에게 제공하거나 애플리케이션에서 파일과 유사한 스트림을 통해 액세스할 수 있습니다.

애플리케이션은 파일 업로드 필드가 있는 웹 양식을 제공하여 사용자가 Blobstore 값을 업로드하도록 요청합니다. 애플리케이션은 Blobstore API를 호출하여 양식의 작업 URL을 생성합니다. 사용자 브라우저는 생성된 URL을 통해 파일을 Blobstore에 직접 업로드합니다. 그러면 Blobstore는 blob을 저장하고, blob 키를 포함하도록 요청을 재작성하고, 애플리케이션의 경로로 요청을 전달합니다. 애플리케이션의 해당 경로에 있는 요청 핸들러는 추가적인 양식 처리를 수행할 수 있습니다.

blob을 제공하기 위해 애플리케이션에서 발신 응답에 헤더를 설정하면, App Engine에서 응답을 blob 값으로 대체합니다.

생성된 blob은 수정할 수 없지만 삭제는 가능합니다. 각 blob에 해당하는 blob 정보 레코드가 Datastore에 저장되며, 이 레코드는 생성 시간 및 콘텐츠 유형과 같은 blob 관련 세부정보를 제공합니다. blob 키를 사용하여 blob 정보 레코드를 가져오고 그 속성을 쿼리할 수 있습니다.

애플리케이션은 API 호출을 사용하여 한 번에 한 부분씩 Blobstore 값을 읽을 수 있습니다. 부분의 최대 크기는 API 반환 값의 최대 크기입니다. 이 크기는 32MB 미만이며 Python에서 google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE 상수로 표시됩니다. 애플리케이션에서는 사용자가 업로드하는 파일 이외에 다른 방법으로 Blobstore 값을 만들거나 수정할 수 없습니다.

Blobstore 사용

애플리케이션은 Blobstore를 사용하여 사용자가 업로드하는 대용량 파일을 수신하고 제공할 수 있습니다. 이렇게 업로드된 파일을 blob이라고 합니다. 애플리케이션은 blob에 직접 액세스하지 않습니다. 대신 애플리케이션은 Datastore에서 BlobInfo 클래스로 표현되는 blob 정보 항목을 통해 blob을 처리합니다.

사용자는 하나 이상의 파일 입력 필드를 포함하는 HTML 양식을 제출하여 blob을 만듭니다. 애플리케이션은 이 양식의 대상(작업)을 얻기 위해 create_upload_url()을 호출하고 애플리케이션에 있는 핸들러의 URL 경로를 함수에 전달합니다. 사용자가 양식을 제출하면 사용자의 브라우저는 지정된 파일을 Blobstore에 직접 업로드합니다. Blobstore는 사용자 요청을 다시 쓰고 업로드된 파일 데이터를 저장하여 업로드된 파일 데이터를 해당 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에 대한 정보 레코드를 만들어 Datastore에 저장하고, 재작성한 요청을 애플리케이션의 지정된 경로에 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 키 자체는 Datastore의 blob 정보 항목에서 계속 액세스할 수 있습니다. 사용자가 양식을 제출한 후 핸들러가 호출된 시점에는 이미 blob이 저장되고 Datastore에 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())

웹 앱 프레임워크는 양식 데이터를 파싱하는 데 유용한 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 키가 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)

웹 앱 프레임워크는 양식 데이터를 파싱하는 데 유용한 다운로드 핸들러 클래스 blobstore_handlers.BlobstoreDownloadHandler를 제공합니다. 자세한 내용은 BlobstoreDownloadHandler 참조를 확인하세요.

모든 애플리케이션 URL에서 blob을 제공할 수 있습니다. 애플리케이션에서 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는 중복 여부와 상관없이 단일 요청에서 다중 바이트 범위(예: 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에서 이미지 서비스 사용

이미지 서비스는 Blobstore 값을 변환 소스로 사용할 수 있습니다. 소스 이미지의 최대 크기는 Blobstore 값의 최대 크기입니다. 이미지 서비스는 변환된 이미지를 여전히 애플리케이션에 반환하므로 변환된 이미지는 32MB보다 작아야 합니다. 이 기능은 사용자가 업로드한 큰 사진의 썸네일 이미지를 만드는 데 유용합니다.

Blobstore 값으로 이미지 서비스를 사용하는 방법에 대한 자세한 내용은 이미지 서비스 문서를 참조하세요.

Google Cloud Storage에서 Blobstore API 사용

Blobstore API를 사용하면 blob을 Blobstore에 저장하는 대신 Cloud Storage에 저장할 수 있습니다. 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 클래스는 다음 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 값에 사용되는 공간은 저장된 데이터(청구 가능 용량) 할당량을 소비합니다. Datastore의 blob 정보 항목은 Datastore 관련 한도에 반영됩니다. Google Cloud Storage는 유료 서비스이며 Cloud Storage 가격표에 따라 비용이 청구됩니다.

시스템 전체의 안전 할당량에 대한 자세한 내용은 할당량을 참조하세요.

시스템 전체의 안전 할당량 외에 Blobstore 사용 시에만 적용되는 한도는 다음과 같습니다.

  • API 호출 한 번으로 애플리케이션에서 읽을 수 있는 Blobstore 데이터의 최대 크기는 32MB입니다.
  • 단일 양식 POST에 최대 500개의 파일을 업로드할 수 있습니다.