はじめに: タスクキュー

タスクキューと App Engine Image API を使用して画像のサイズを変更する方法について説明します。

タスクキューでは、直接的なユーザー操作の外でコードが実行されるので、バックグラウンドでタスクを実行できます。このガイドでは、イメージを Cloud Storage に追加した後、タスクキューを使用してタスクを実行します。次のタスクがタスクキューで実行されます。

  1. Cloud Storage にアップロードされた画像ファイルを取得します。
  2. Image API を使用してサムネイル画像にサイズ変更します。
  3. 結果のサムネイルを Cloud Storage に保存します。

App Engine Java 8 ランタイムでは、AWTJava2D などの Java ネイティブ画像処理クラスもサポートされています。

始める前に

  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 タスクキューがありますが、作業タイプに応じて別のタスクキューを作成することもできます。たとえば、画像サイズの変更に使用するタスクキューと、アプリのデータベースの更新に使用するタスクキューを作成できます。

キューを追加するには、queue.xml ファイルを App Engine プロジェクトの WEB-INF ディレクトリに作成します。タスクキューには名前と実行レートを指定する必要があります。

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

この例の resize-image という名前のキューでは、1 時間に 60 回、つまり 1 分に 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 にマップされているタスクハンドラが実行されます。タスクハンドラは、タスクが成功するまで実行を試行する Java サーブレットです。

この例では、タスクハンドラは次の 3 つのタスクを行います。

  • 呼び出し元が指定した画像を 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() メソッドを使用して画像をサムネイルに変更しています。この処理を行うため、Cloud Storage から InputChannel に画像を読み込み、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>

キューからの 1 つのタスクの削除

1 つのキューから 1 つのタスクを削除するには deleteTask() を使用します。

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

キューからのすべてのタスクの削除

1 つのキューからすべてのタスクを削除するには 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 は実際の Cloud プロジェクトの ID に置き換えます。pom.xml ファイルですでにプロジェクト ID を指定している場合は、実行するコマンドに -Dapp.deploy.projectId プロパティを含める必要はありません。

Maven によってアプリがデプロイされた後、次のように入力すると、新しいアプリでウェブブラウザのタブが自動的に開きます。

gcloud app browse

次のステップ

このガイドでは、タスクキューを使用して画像のサムネイルを作成し、Cloud Storage に保存する方法を示していますが、この方法は Cloud DatastoreCloud SQL などの他のストレージ サービスでも使用できます。