시작하기: 태스크 큐

태스크 큐와 App Engine Image API를 사용하여 이미지의 크기를 조정하는 방법을 알아봅니다.

태스크 큐는 코드를 직접적인 사용자 상호작용 밖에서 실행하여 작업이 백그라운드에서 이루어지게 합니다. 이 가이드에서는 Cloud Storage에 이미지를 추가한 후 태스크 큐를 사용하여 작업을 수행합니다. 태스크 큐에서 수행할 작업은 다음과 같습니다.

  1. Cloud Storage에 방금 업로드한 이미지 파일을 가져옵니다.
  2. Image API를 사용하여 이 파일의 크기를 썸네일 이미지로 조정합니다.
  3. 그 결과로 생성된 썸네일을 Cloud Storage에 저장합니다.

App Engine 자바 8 런타임은 AWTJava2D와 같은 자바의 기본 이미지 조작 클래스도 지원합니다.

시작하기 전에

  1. 개발자 환경을 구성하고 App Engine 프로젝트를 만듭니다.

  2. 이 가이드에서는 Apache Commons IOUtils 라이브러리를 사용합니다. IOUtils 라이브러리를 App Engine 프로젝트에 포함하려면 다음 안내를 따르세요.

    pom.xml에 추가합니다.

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.5</version>
    </dependency>
    

라이브러리 가져오기

이 가이드에 제공된 샘플 코드는 다음과 같은 가져오기를 사용합니다.

import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;
import org.apache.commons.io.IOUtils;

태스크 큐 만들기

App Engine이 default 태스크 큐를 제공하는 동안 작업 유형마다 각기 다른 태스크 큐를 만들 수 있습니다. 예를 들어 이미지 크기를 조절하는 태스크 큐와 앱의 데이터베이스를 업데이트하는 태스크 큐를 각각 만들 수 있습니다.

큐를 추가하려면 App Engine 프로젝트의 WEB-INF 디렉터리에 queue.xml 파일을 만듭니다. 태스크 큐의 이름과 실행 속도를 지정해야 합니다.

<?xml version="1.0" encoding="UTF-8"?>
  <queue-entries>
    <queue>
      <name>resize-image</name>
      <rate>60/h</rate>
    </queue>
  </queue-entries>

resize-image라는 이 예시 큐는 시간당 60회 또는 분당 1회의 실행 속도를 정의합니다. 큐 옵션의 전체 목록을 보려면 queue.xml 참조를 확인하세요.

태스크 큐에는 태스크 요청자와 태스크 핸들러라는 2가지 구성요소가 있습니다. 요청자는 태스크를 큐에 추가하여 태스크 핸들러로 보냅니다.

작업을 대기열에 추가하기

태스크를 큐에 추가하려면 다음 안내를 따르세요.

  1. QueueFactory.getQueue()를 사용하여 태스크 큐 객체를 만들고 queue.xml에 정의된 큐 이름을 지정합니다.

    Queue imageResizeQueue; // Taskqueue queue
    
    @Override
    public void init() throws ServletException {
    
      // Setup Cloud Storage service
      gcsService =
          GcsServiceFactory.createGcsService(
              new RetryParams.Builder()
                  .initialRetryDelayMillis(10)
                  .retryMaxAttempts(10)
                  .totalRetryPeriodMillis(15000)
                  .build());
    
      // Initialize the queue object with the specific queue
      imageResizeQueue = QueueFactory.getQueue([QUEUE-NAME]);
    
      // Cloud SQL connection setup
      try {
        final String url = System.getProperty("cloudsql"); // Cloud SQL server URI
    
        try {
          conn = DriverManager.getConnection(url); // Connect to the database
    
          Statement createTable; // SQL statement
    
          // Batch SQL table creation commands
          createTable.addBatch(createContentTableSql);
          createTable.addBatch(createUserTableSql);
          createTable.addBatch(createImageTableSql);
          createTable.addBatch(createBlogPostImageTableSql);
          conn.createTable.executeBatch(); // Execute batch
    
        } catch (SQLException e) {
          throw new ServletException("Unable to connect to Cloud SQL", e);
        }
    
      } finally {
        // Nothing really to do here.
      }
    
    }
    
  2. 태스크를 Queue 객체에 추가합니다. 코드 샘플과 같이 imageResizeQueue.add()는 태스크를 imageResizeQueue 객체에 추가합니다.

    try {
      // Add a queued task to create a thumbnail of the uploaded image
      imageResizeQueue.add(
          TaskOptions.Builder.withUrl("/tasks/imageresize").param("filename", filename));
    }
    

    핸들러에 보낸 매개변수와 함께 TaskOptions.Builder.withUrl()을 사용하여 태스크 핸들러의 URI를 지정합니다.

    이 예시에서 URI는 /tasks/imageresize이고 매개변수는 처리할 이미지의 파일 이름이 있는 filename이라는 변수입니다.

작업 핸들러 만들기

태스크를 큐에 추가하면 URI /tasks/imageresize에 매핑된 태스크 핸들러가 실행됩니다. 태스크 핸들러는 작업이 성공할 때까지 작업 수행을 시도하는 자바 서블릿입니다.

이 예에서 작업 핸들러는 세 가지 작업을 수행합니다.

  • Cloud Storage에서 호출자로 지정된 이미지를 검색합니다.

  • 이 샘플의 App Engine Image API를 사용하여 이미지를 썸네일 이미지로 변환합니다.

  • 변환된 이미지(썸네일)을 Cloud Storage에 저장합니다.

태스크 핸들러를 만들려면 다음 안내를 따르세요.

  1. 핸들러를 URI /tasks/imageresize에 매핑하는 주석을 추가합니다.

     @WebServlet(name = "imageResize", description = "Task queue handler", urlPatterns = "/tasks/imageresize")
     public class imageResize extends HttpServlet {
    
       // Task handler functionality
    
     }
    
  2. Cloud Storage 사용 가이드에 설명된 대로 Cloud Storage에 대한 연결을 설정하고 Cloud Storage에서 이미지를 검색합니다.

     public void init() throws ServletException {
    
      // initiate GcsService
      GcsService gcsService =
        GcsServiceFactory.createGcsService(
            new RetryParams.Builder()
                .initialRetryDelayMillis(10)
                .retryMaxAttempts(10)
                .totalRetryPeriodMillis(15000)
                .build());
    }
    
  3. Cloud Storage에서 이미지를 검색하기 위해 제공된 파일 이름을 사용하여 수신되는 태스크 큐 요청을 처리합니다.

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    
      String filename = req.getParameter("filename"); // Get the filename passed from the task requestor
      GcsFilename gcsFile = new GcsFilename(bucket, filename); // Create a valid Cloud Storage filename
    
      GcsInputChannel readChannel = gcsService.openPrefetchingReadChannel(gcsFile, 0, BUFFER_SIZE); // Read the file from Cloud Storage
    
  4. ImagesService 객체를 사용하여 이미지 크기를 조정합니다.

    // Get an instance of the ImagesService we can use to transform images.
    ImagesService imagesService = ImagesServiceFactory.getImagesService();
    
    // Make an image directly from a byte array, and transform it.
    Image image =
        ImagesServiceFactory.makeImage(IOUtils.toByteArray(Channels.newInputStream(readChannel)));
    Transform resize = ImagesServiceFactory.makeResize(100, 50); // resize image to 100x50
    Image resizedImage = imagesService.applyTransform(resize, image);
    
    // Write the transformed image back to a Cloud Storage object.
    gcsService.createOrReplace(
        new GcsFilename(bucket, "thumbnail_" + filename),
        new GcsFileOptions.Builder().acl("public-read").build(),
        ByteBuffer.wrap(resizedImage.getImageData()));
    

    위의 스니펫은 Image API makeResize() 메서드를 사용하여 이미지를 미리보기 이미지 크기로 조정합니다. 이렇게 하려면 InputChannel에서 Cloud Storage의 이미지를 읽고 IOUtils.toByteArray()를 사용하여 ByteArray로 변환합니다.

    변환을 적용하면 새로운 이미지의 파일 이름에 문자열 thumbnail_이 추가되고, 공개적으로 읽을 수 있고 Cloud Storage에 기록할 수 있도록 권한이 설정됩니다.

태스크 핸들러 URL 보호

데이터 수정과 같은 민감한 작업을 수행하는 작업을 보호하여, 외부 사용자가 이를 직접 호출할 수 없도록 해야 합니다. 이렇게 하려면 태스크 액세스를 App Engine 관리자로 제한하여 사용자가 태스크 URL에 액세스하지 못하게 합니다. 이러한 제한은 App Engine 앱에서 수신되는 태스크 요청에는 적용되지 않습니다.

이 예시에서 태스크 핸들러의 /tasks/ 폴더에 URL이 있습니다. /tasks/ 폴더에 대한 액세스를 App Engine 관리자로 제한하려면 다음을 프로젝트의 web.xml에 추가합니다.

<security-constraint>
    <web-resource-collection>
        <web-resource-name>tasks</web-resource-name>
        <url-pattern>/tasks/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

큐에서 단일 태스크 제거

큐에서 단일 태스크를 제거하려면 deleteTask()를 사용합니다.

private void removeTask(Queue queue, String taskName) {
  queue.deleteTask(taskName); // remove a task from a queue
}

큐에서 모든 태스크 제거

큐에서 모든 태스크를 제거하려면 purge()를 사용합니다. purge를 사용하면 큐의 모든 태스크를 제거하는 데 최대 1분이 걸립니다.

private void purgeQueue(Queue queue) {
  queue.purge(); // remove all tasks from a queue
}

대기열에서 모든 작업을 제거하는 데 1분 정도 걸릴 수 있으므로 새로운 작업을 대기열에 추가하기 전에 몇 초 기다려야 합니다.

태스크 큐 삭제

태스크 큐를 삭제하려면 프로젝트의 queue.xml 파일에서 항목을 삭제하고 재배포합니다.

App Engine에 배포

Maven을 사용하여 앱을 App Engine에 배포할 수 있습니다.

프로젝트의 루트 디렉터리로 이동하여 다음을 입력합니다.

mvn package appengine:deploy -Dapp.deploy.projectId=PROJECT_ID

PROJECT_ID를 Google Cloud 프로젝트의 ID로 바꿉니다. pom.xml 파일에 이미 프로젝트 ID가 지정된 경우 실행할 명령어에 -Dapp.deploy.projectId 속성을 포함하지 않아도 됩니다.

Maven이 앱을 배포한 후에 다음을 입력하여 새 앱에서 웹브라우저 탭을 자동으로 엽니다.

gcloud app browse

다음 단계

이 가이드에서는 태스크 큐를 사용하여 이미지 썸네일을 만들고 Cloud Storage에 저장하는 방법을 알아보았습니다. 이 방법을 Cloud Datastore 또는 Cloud SQL과 같은 다른 스토리지 서비스에서도 사용할 수 있습니다.