기존 번들 서비스용 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 정보 기록을 가져와 해당 속성을 쿼리할 수 있습니다.

Blobstore 사용

애플리케이션은 Blobstore를 사용하여 사용자가 업로드하는 대용량 파일을 수신하고 제공할 수 있습니다. 이렇게 업로드된 파일을 blob이라고 합니다. 애플리케이션은 파일 이름으로 blob에 직접 액세스하지 않습니다. 대신 애플리케이션이 appengine.BlobKey 유형을 통해 blob을 참조합니다.

사용자는 하나 이상의 파일 입력 필드를 포함하는 HTML 양식을 제출하여 blob을 만듭니다. 애플리케이션은 blobstore.UploadURL을 이 양식의 대상(작업)으로 설정하고 이 함수에 애플리케이션의 핸들러 URL 경로를 전달합니다. 사용자가 양식을 제출하면 사용자의 브라우저는 지정된 파일을 Blobstore에 직접 업로드합니다. Blobstore는 사용자 요청을 다시 쓰고 업로드된 파일 데이터를 저장하여 업로드된 파일 데이터를 해당 blob 키 하나 이상으로 바꿉니다. 그런 다음 다시 쓴 요청을 사용자가 blobstore.UploadURL에 제공한 URL 경로의 핸들러로 전달합니다. 이 핸들러는 blob 키를 기반으로 추가적인 처리를 수행할 수 있습니다. 마지막으로, 핸들러는 헤더만 있는 리디렉션 응답(301, 302, 303)(일반적으로 blob 업로드의 상태를 나타내기 위한 다른 페이지로의 브라우저 리디렉션)을 반환합니다.

애플리케이션이 blobstore.Reader를 사용하여 Blobstore 값의 일부를 읽을 수 있습니다.

blob 업로드

blob을 만들고 업로드하는 절차는 다음과 같습니다.

1. 업로드 URL 만들기

blobstore.UploadURL을 호출하여 사용자가 작성할 양식의 업로드 URL을 만들고 양식의 POST가 작성되면 로드할 애플리케이션 경로를 전달합니다.

ctx := appengine.NewContext(r)
uploadURL, err := blobstore.UploadURL(ctx, "/upload", nil)
if err != nil {
	serveError(ctx, w, err)
	return
}

2. 업로드 양식 만들기

양식은 파일 업로드 필드를 포함해야 하며 양식의 enctypemultipart/form-data로 설정되어야 합니다. 사용자가 양식을 제출하면 Blobstore API가 POST를 처리하여 blob을 만듭니다. 또한 API는 blob에 대한 정보 레코드를 만들어 Datastore에 저장하고, 재작성한 요청을 애플리케이션의 지정된 경로에 blob 키로 전달합니다.

	var rootTemplate = template.Must(template.New("root").Parse(rootTemplateHTML))

	const rootTemplateHTML = `
<html><body>
<form action="{{.}}" method="POST" enctype="multipart/form-data">
Upload File: <input type="file" name="file"><br>
<input type="submit" name="submit" value="Submit">
</form></body></html>
`

3. 업로드 핸들러 구현

이 핸들러에서는 애플리케이션의 나머지 데이터 모델과 함께 blob 키를 저장할 수 있습니다. blob 키 자체는 Datastore의 blob 정보 항목에서 계속 액세스할 수 있습니다. 사용자가 양식을 제출한 후 핸들러가 호출된 시점에는 이미 blob이 저장되고 Datastore에 blob 정보가 추가된 상태입니다. 애플리케이션에 blob을 유지하지 않으려면 blob이 고립된 상태가 되지 않도록 즉시 삭제해야 합니다.

ctx := appengine.NewContext(r)
blobs, _, err := blobstore.ParseUpload(r)
if err != nil {
	serveError(ctx, w, err)
	return
}
file := blobs["file"]
if len(file) == 0 {
	log.Errorf(ctx, "no file uploaded")
	http.Redirect(w, r, "/", http.StatusFound)
	return
}
http.Redirect(w, r, "/serve/?blobKey="+string(file[0].BlobKey), http.StatusFound)

Blobstore는 사용자의 요청을 재작성하면서 업로드된 파일에서 MIME 부분의 본문을 비우고 blob 키를 MIME 부분 헤더로 추가합니다. 다른 양식 필드와 부분은 모두 그대로 보존하여 업로드 핸들러에 전달합니다. 콘텐츠 유형을 지정하지 않으면 Blobstore는 파일 확장자로 콘텐츠 유형을 유추합니다. 콘텐츠 유형을 확인할 수 없으면 새로 생성된 blob에 application/octet-stream 콘텐츠 유형이 할당됩니다.

blob 제공

blob을 제공하려면 blob 다운로드 핸들러를 애플리케이션의 경로로 포함해야 합니다. 이 핸들러는 원하는 blob의 blob 키를 blobstore.Send으로 전달해야 합니다. 이 예시에서는 다운로드 핸들러에 blob 키를 URL 인수 r.FormValue("blobKey")로 전달합니다. 실제로 다운로드 핸들러가 blob 키를 가져오는 데 사용할 수 있는 방법에는 제한이 없습니다. 예를 들어 다른 메서드를 거치거나 사용자 작업을 통할 수 있습니다.

blobstore.Send(w, appengine.BlobKey(r.FormValue("blobKey")))

모든 애플리케이션 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)를 지원하지 않습니다.

전체 샘플 애플리케이션

다음 샘플 애플리케이션에서 애플리케이션의 기본 URL은 사용자에게 업로드할 파일을 요청하는 양식을 로드하고, 업로드 핸들러는 다운로드 핸들러를 즉시 호출하여 데이터를 제공합니다. 샘플 애플리케이션은 단순하게 표현되었지만, 실제로는 기본 URL을 사용하여 업로드 데이터를 요청하거나 방금 업로드한 blob을 즉시 제공하지 않습니다.


package blobstore_example

import (
	"context"
	"html/template"
	"io"
	"net/http"

	"google.golang.org/appengine"
	"google.golang.org/appengine/blobstore"
	"google.golang.org/appengine/log"
)

func serveError(ctx context.Context, w http.ResponseWriter, err error) {
	w.WriteHeader(http.StatusInternalServerError)
	w.Header().Set("Content-Type", "text/plain")
	io.WriteString(w, "Internal Server Error")
	log.Errorf(ctx, "%v", err)
}

var rootTemplate = template.Must(template.New("root").Parse(rootTemplateHTML))

const rootTemplateHTML = `
<html><body>
<form action="{{.}}" method="POST" enctype="multipart/form-data">
Upload File: <input type="file" name="file"><br>
<input type="submit" name="submit" value="Submit">
</form></body></html>
`

func handleRoot(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	uploadURL, err := blobstore.UploadURL(ctx, "/upload", nil)
	if err != nil {
		serveError(ctx, w, err)
		return
	}
	w.Header().Set("Content-Type", "text/html")
	err = rootTemplate.Execute(w, uploadURL)
	if err != nil {
		log.Errorf(ctx, "%v", err)
	}
}

func handleServe(w http.ResponseWriter, r *http.Request) {
	blobstore.Send(w, appengine.BlobKey(r.FormValue("blobKey")))
}

func handleUpload(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	blobs, _, err := blobstore.ParseUpload(r)
	if err != nil {
		serveError(ctx, w, err)
		return
	}
	file := blobs["file"]
	if len(file) == 0 {
		log.Errorf(ctx, "no file uploaded")
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}
	http.Redirect(w, r, "/serve/?blobKey="+string(file[0].BlobKey), http.StatusFound)
}

func init() {
	http.HandleFunc("/", handleRoot)
	http.HandleFunc("/serve/", handleServe)
	http.HandleFunc("/upload", handleUpload)
}

Google Cloud Storage에서 Blobstore API 사용

Blobstore API를 사용하면 blob을 Blobstore에 저장하는 대신 Cloud Storage에 저장할 수 있습니다. Google Cloud Storage 문서의 설명대로 버킷을 설정하고 UploadURL 함수에 제공하는 UploadURLOptions에 버킷과 파일 이름을 지정해야 합니다. 업로드 핸들러에서 반환된 blob의 반환된 맵을 처리하고 나중에 blob을 검색할 때 필요한 Google Cloud Storage 파일 이름을 명시적으로 저장해야 합니다.

Blobstore API를 사용하여 Cloud Storage 객체를 제공할 수도 있습니다. Cloud Storage 객체를 제공하려면 BlobKeyForFile을 사용하여 blob 제공에서 설명하는 필수 blobkey를 생성합니다.

할당량 및 한도

Blobstore 값에 사용되는 공간은 저장된 데이터(청구 가능 용량) 할당량을 소비합니다. Datastore의 blob 정보 항목은 Datastore 관련 한도에 반영됩니다. Google Cloud Storage는 유료 서비스이며 Cloud Storage 가격표에 따라 비용이 청구됩니다.

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

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

  • API 호출 한 번으로 애플리케이션에서 읽을 수 있는 Blobstore 데이터의 최대 크기는 32MB입니다.
  • 단일 양식 POST에 최대 500개의 파일을 업로드할 수 있습니다.
이러한 크기 한도 조정에 대한 자세한 내용은 Send 함수 문서를 참조하세요.