자바 8용 Blobstore API 개요

참고: blob 데이터를 저장할 때 Blobstore 대신 Google Cloud Storage를 사용하는 것이 좋습니다.

애플리케이션에서 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 미만이며 자바의 com.google.appengine.api.blobstore.BlobstoreService.MAX_BLOB_FETCH_SIZE 상수로 표시됩니다. 애플리케이션에서는 사용자가 업로드하는 파일 이외에 다른 방법으로 Blobstore 값을 만들거나 수정할 수 없습니다.

Blobstore 사용

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

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

애플리케이션은 파일과 유사한 스트리밍 인터페이스를 사용하여 Blobstore 값의 일부분을 읽을 수 있습니다. BlobstoreInputStream 클래스를 참조하세요.

blob 업로드

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

1. 업로드 URL 만들기

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

<body>
    <form action="<%= blobstoreService.createUploadUrl("/upload") %>" method="post" enctype="multipart/form-data">
        <input type="file" name="myFile">
        <input type="submit" value="Submit">
    </form>
</body>

위의 예는 업로드 양식을 JSP로 작성한 경우입니다.

2. 업로드 양식 만들기

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

3. 업로드 핸들러 구현

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

다음 코드에서 getUploads는 업로드된 blob 집합을 반환합니다. Map 객체는 업로드 필드의 이름을 포함된 blob에 연결하는 목록입니다.

Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
List<BlobKey> blobKeys = blobs.get("myFile");

if (blobKeys == null || blobKeys.isEmpty()) {
    res.sendRedirect("/");
} else {
    res.sendRedirect("/serve?blob-key=" + blobKeys.get(0).getKeyString());
}

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

blob 제공

blob을 제공하려면 blob 다운로드 핸들러를 애플리케이션의 경로로 포함해야 합니다. 이 핸들러는 원하는 blob의 blob 키를 blobstoreService.serve(blobKey, res);으로 전달해야 합니다. 이 예시에서는 다운로드 핸들러에 blob 키를 URL 인수 (req.getParameter('blob-key'))로 전달합니다. 실제로 다운로드 핸들러는 다른 메서드 또는 사용자 작업 등을 통해 blob 키를 얻을 수 있습니다.

public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws IOException {
        BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
        blobstoreService.serve(blobKey, res);

모든 애플리케이션 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을 즉시 제공하지 않습니다.

// file Upload.java

import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

public class Upload extends HttpServlet {
    private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
        List<BlobKey> blobKeys = blobs.get("myFile");

        if (blobKeys == null || blobKeys.isEmpty()) {
            res.sendRedirect("/");
        } else {
            res.sendRedirect("/serve?blob-key=" + blobKeys.get(0).getKeyString());
        }
    }
}

// file Serve.java

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

public class Serve extends HttpServlet {
    private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws IOException {
            BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
            blobstoreService.serve(blobKey, res);
        }
}

// file index.jsp

<%@ page import="com.google.appengine.api.blobstore.BlobstoreServiceFactory" %>
<%@ page import="com.google.appengine.api.blobstore.BlobstoreService" %>

<%
    BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
%>

<html>
    <head>
        <title>Upload Test</title>
    </head>
    <body>
        <form action="<%= blobstoreService.createUploadUrl("/upload") %>" method="post" enctype="multipart/form-data">
            <input type="text" name="foo">
            <input type="file" name="myFile">
            <input type="submit" value="Submit">
        </form>
    </body>
</html>

// web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <servlet>
    <servlet-name>Upload</servlet-name>
    <servlet-class>Upload</servlet-class>
  </servlet>

  <servlet>
    <servlet-name>Serve</servlet-name>
    <servlet-class>Serve</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Upload</servlet-name>
    <url-pattern>/upload</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>Serve</servlet-name>
    <url-pattern>/serve</url-pattern>
  </servlet-mapping>

</web-app>

Blobstore에서 이미지 서비스 사용

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

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

Google Cloud Storage에서 Blobstore API 사용

Blobstore API를 사용하여 blob을 Blobstore에 저장하는 대신 Cloud Storage에 저장할 수 있습니다. 이렇게 하려면 Google Cloud Storage 문서의 설명대로 버킷을 설정하고, BlobstoreService createUploadUrl에 버킷과 파일 이름을 지정하고, UploadOptions 매개변수에 버킷 이름을 지정해야 합니다. 업로드 핸들러에서 반환된 FileInfo 메타데이터를 처리하고 나중에 blob을 가져올 때 필요한 Google Cloud Storage 파일 이름을 명시적으로 저장해야 합니다.

Blobstore API를 사용하여 Cloud Storage 객체를 제공할 수도 있습니다.

다음은 이 방법을 보여주는 코드 스니펫입니다. 이 샘플은 요청의 버킷 이름과 객체 이름을 가져오는 요청 핸들러에 있습니다. Blobstore 서비스를 만들고 이를 통해 제공된 버킷 이름과 객체 이름으로 Google Cloud Storage용 blob 키를 만듭니다.

BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
BlobKey blobKey = blobstoreService.createGsBlobKey(
    "/gs/" + fileName.getBucketName() + "/" + fileName.getObjectName());
blobstoreService.serve(blobKey, resp);

할당량 및 한도

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

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

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

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