웹 앱 Blobstore 핸들러

웹 앱에는 Blobstore API 작업을 위한 요청 핸들러 클래스가 포함되어 있습니다. BlobstoreUploadHandler는 추가적인 처리를 위해 Blobstore를 통해 전달된 업로드 요청을 BlobInfo 레코드로 파싱하는 로직을 제공합니다. BlobstoreDownloadHandler를 사용하면 어느 경로에서든 Blobstore 값을 쉽게 제공할 수 있습니다.

BlobstoreUploadHandler

값은 앱의 사용자 또는 관리자가 게시하는 파일 업로드를 통해 Blobstore에 추가됩니다. 앱은 파일 업로드 필드와 업로드를 Blobstore로 전달하는 양식 액션을 포함하여 웹 양식을 게시합니다. 앱은 함수create_upload_url()를 호출하여 양식 액션 URL을 가져오고 여기에 사용자가 파일을 업로드할 때 호출되는 앱 핸들러의 URL을 전달합니다. 웹 앱 애플리케이션은 BlobstoreUploadHandler 클래스의 서브클래스를 이 URL의 핸들러로 사용할 수 있습니다.

get_uploads() 메서드는 요청에서 업로드되는 파일마다 하나씩 할당되는 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에서 파일을 다시 검색할 때 이 이름이 필요하기 때문입니다. 각 업로드에 해당하는 FileInfo 레코드 목록을 반환하는 get_file_infos 함수를 사용합니다. 전체 Cloud Storage 객체 이름, 콘텐츠 유형, 생성 시간, 기타 데이터는 FileInfo에서 확인할 수 있습니다. 자세한 내용은 링크를 참조하세요.

BlobstoreDownloadHandler

애플리케이션은 Blobstore 값을 제공하기 위해 X-AppEngine-BlobKey 헤더를 문자열 형식의 Blobstore 키 값으로 설정합니다. App Engine은 응답에서 이 헤더를 확인하고 BLOB의 값을 응답의 본문으로 제공합니다. 웹 앱 핸들러 클래스 BlobstoreDownloadHandler를 사용하면 응답에서 이 값을 쉽게 설정할 수 있습니다.

send_blob() 메서드는 BlobKey 객체, 문자열 키, BlobInfoblob_key_or_info 인수로 사용하고 blob 값이 사용자에게 제공되도록 응답 데이터를 설정합니다. 이 메서드는 저장된 blob 값의 MIME 콘텐츠 유형을 재정의하는 선택적 content_type 인수를 사용합니다. 기본적으로 BLOB는 BLOB를 업로드한 클라이언트가 설정한 콘텐츠 유형, 파일 이름에서 파생된 콘텐츠 유형, 다른 유형 정보가 없는 경우 일반적인 유형으로 제공됩니다.

send_blob() 메서드는 blob 데이터가 원시 응답 데이터로 전송되는지 아니면 파일 이름이 지정된 MIME 첨부파일로 전송되는지 여부를 결정하는 save_as 인수를 허용합니다. 인수가 문자열이면 blob이 첨부파일로 전송되고 문자열 값이 파일 이름으로 사용됩니다. True이고 blob_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() 메서드에 바이트 색인 범위를 제공할 수 있습니다. 첫 번째는 범위를 startend 인수로 지정하는 것입니다.

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

기본적으로 BlobstoreDownloadHandler는 요청의 range 헤더를 준수합니다. 원본 범위 헤더를 사용하지 못하도록 차단하려면 use_range=False 매개변수를 send_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)